OrderControllerTest.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852
  1. <?php
  2. namespace Tests\Feature;
  3. use App\Jobs\GenerateInstallationPack;
  4. use App\Models\Dictionary\Area;
  5. use App\Models\Dictionary\District;
  6. use App\Models\File;
  7. use App\Models\MafOrder;
  8. use App\Models\ObjectType;
  9. use App\Models\Order;
  10. use App\Models\OrderStatus;
  11. use App\Models\Product;
  12. use App\Models\ProductSKU;
  13. use App\Models\Role;
  14. use App\Models\User;
  15. use Illuminate\Foundation\Testing\RefreshDatabase;
  16. use Illuminate\Http\UploadedFile;
  17. use Illuminate\Support\Facades\Bus;
  18. use Illuminate\Support\Facades\Storage;
  19. use Tests\TestCase;
  20. class OrderControllerTest extends TestCase
  21. {
  22. use RefreshDatabase;
  23. protected $seed = true;
  24. private User $adminUser;
  25. private User $managerUser;
  26. private User $brigadierUser;
  27. protected function setUp(): void
  28. {
  29. parent::setUp();
  30. $this->adminUser = User::factory()->create(['role' => Role::ADMIN]);
  31. $this->managerUser = User::factory()->create(['role' => Role::MANAGER]);
  32. $this->brigadierUser = User::factory()->create(['role' => Role::BRIGADIER]);
  33. }
  34. // ==================== Authentication ====================
  35. public function test_guest_cannot_access_orders_index(): void
  36. {
  37. $response = $this->get(route('order.index'));
  38. $response->assertRedirect(route('login'));
  39. }
  40. public function test_authenticated_user_can_access_orders_index(): void
  41. {
  42. $response = $this->actingAs($this->managerUser)
  43. ->get(route('order.index'));
  44. $response->assertStatus(200);
  45. $response->assertViewIs('orders.index');
  46. }
  47. // ==================== Index ====================
  48. public function test_orders_index_displays_orders(): void
  49. {
  50. $order = Order::factory()->create([
  51. 'object_address' => 'ул. Проверочная, д. 101',
  52. ]);
  53. $response = $this->actingAs($this->managerUser)
  54. ->get(route('order.index'));
  55. $response->assertStatus(200);
  56. $response->assertSee($order->object_address);
  57. }
  58. public function test_brigadier_sees_only_assigned_orders(): void
  59. {
  60. $assignedOrder = Order::factory()->create([
  61. 'brigadier_id' => $this->brigadierUser->id,
  62. 'object_address' => 'ул. Бригадирская, д. 10',
  63. ]);
  64. $otherOrder = Order::factory()->create([
  65. 'brigadier_id' => User::factory()->create(['role' => Role::BRIGADIER])->id,
  66. 'object_address' => 'ул. Чужая, д. 20',
  67. ]);
  68. $response = $this->actingAs($this->brigadierUser)
  69. ->get(route('order.index'));
  70. $response->assertStatus(200);
  71. $response->assertSee($assignedOrder->object_address);
  72. $response->assertDontSee($otherOrder->object_address);
  73. }
  74. public function test_brigadier_does_not_see_handed_over_orders_in_index(): void
  75. {
  76. $visibleOrder = Order::factory()->create([
  77. 'brigadier_id' => $this->brigadierUser->id,
  78. 'order_status_id' => Order::STATUS_IN_MOUNT,
  79. 'object_address' => 'ул. Видимая, д. 11',
  80. ]);
  81. $hiddenOrder = Order::factory()->create([
  82. 'brigadier_id' => $this->brigadierUser->id,
  83. 'order_status_id' => Order::STATUS_HANDED_OVER,
  84. 'object_address' => 'ул. Сданная, д. 12',
  85. ]);
  86. $response = $this->actingAs($this->brigadierUser)
  87. ->get(route('order.index'));
  88. $response->assertStatus(200);
  89. $response->assertSee($visibleOrder->object_address);
  90. $response->assertDontSee($hiddenOrder->object_address);
  91. }
  92. // ==================== Create ====================
  93. public function test_can_view_create_order_form(): void
  94. {
  95. $response = $this->actingAs($this->adminUser)
  96. ->get(route('order.create'));
  97. $response->assertStatus(200);
  98. $response->assertViewIs('orders.edit');
  99. }
  100. // ==================== Store ====================
  101. public function test_can_create_new_order(): void
  102. {
  103. $district = District::factory()->create();
  104. $area = Area::factory()->create();
  105. $objectType = ObjectType::factory()->create();
  106. $response = $this->actingAs($this->managerUser)
  107. ->post(route('order.store'), [
  108. 'name' => 'Тестовый заказ',
  109. 'user_id' => $this->managerUser->id,
  110. 'district_id' => $district->id,
  111. 'area_id' => $area->id,
  112. 'object_address' => 'ул. Тестовая, д. 1',
  113. 'object_type_id' => $objectType->id,
  114. 'comment' => 'Тестовый комментарий',
  115. ]);
  116. $response->assertRedirect();
  117. $this->assertDatabaseHas('orders', [
  118. 'object_address' => 'ул. Тестовая, д. 1',
  119. 'order_status_id' => Order::STATUS_NEW,
  120. ]);
  121. }
  122. public function test_creating_order_sets_tg_group_name(): void
  123. {
  124. $district = District::factory()->create(['shortname' => 'ЦАО']);
  125. $area = Area::factory()->create(['name' => 'Тверской']);
  126. $objectType = ObjectType::factory()->create();
  127. $this->actingAs($this->managerUser)
  128. ->post(route('order.store'), [
  129. 'name' => 'Тестовый заказ',
  130. 'user_id' => $this->managerUser->id,
  131. 'district_id' => $district->id,
  132. 'area_id' => $area->id,
  133. 'object_address' => 'ул. Пушкина',
  134. 'object_type_id' => $objectType->id,
  135. ]);
  136. $order = Order::where('object_address', 'ул. Пушкина')->first();
  137. $this->assertStringContainsString('ЦАО', $order->tg_group_name);
  138. $this->assertStringContainsString('Тверской', $order->tg_group_name);
  139. }
  140. public function test_can_update_existing_order(): void
  141. {
  142. $order = Order::factory()->create([
  143. 'object_address' => 'Старый адрес',
  144. ]);
  145. $response = $this->actingAs($this->managerUser)
  146. ->post(route('order.store'), [
  147. 'id' => $order->id,
  148. 'name' => $order->name,
  149. 'user_id' => $order->user_id,
  150. 'district_id' => $order->district_id,
  151. 'area_id' => $order->area_id,
  152. 'object_address' => 'Новый адрес',
  153. 'object_type_id' => $order->object_type_id,
  154. ]);
  155. $response->assertRedirect();
  156. $this->assertDatabaseHas('orders', [
  157. 'id' => $order->id,
  158. 'object_address' => 'Новый адрес',
  159. ]);
  160. }
  161. public function test_manager_can_update_ready_date(): void
  162. {
  163. $order = Order::factory()->create([
  164. 'ready_date' => '2026-04-01',
  165. ]);
  166. $response = $this->actingAs($this->managerUser)
  167. ->post(route('order.store'), [
  168. 'id' => $order->id,
  169. 'ready_date' => '2026-04-25',
  170. ]);
  171. $response->assertRedirect();
  172. $this->assertDatabaseHas('orders', [
  173. 'id' => $order->id,
  174. 'ready_date' => '2026-04-25',
  175. ]);
  176. }
  177. // ==================== Show ====================
  178. public function test_can_view_order_details(): void
  179. {
  180. $order = Order::factory()->create([
  181. 'object_address' => 'ул. Детальная, д. 5',
  182. ]);
  183. $response = $this->actingAs($this->managerUser)
  184. ->get(route('order.show', $order));
  185. $response->assertStatus(200);
  186. $response->assertViewIs('orders.show');
  187. $response->assertSee($order->object_address);
  188. }
  189. public function test_order_details_show_area_and_district(): void
  190. {
  191. $district = District::factory()->create(['name' => 'Центральный округ']);
  192. $area = Area::factory()->create([
  193. 'district_id' => $district->id,
  194. 'name' => 'Тверской район',
  195. ]);
  196. $order = Order::factory()->create([
  197. 'district_id' => $district->id,
  198. 'area_id' => $area->id,
  199. ]);
  200. $response = $this->actingAs($this->managerUser)
  201. ->get(route('order.show', $order));
  202. $response->assertOk();
  203. $response->assertSee('Центральный округ');
  204. $response->assertSee('Тверской район');
  205. }
  206. public function test_brigadier_cannot_view_handed_over_order_details(): void
  207. {
  208. $order = Order::factory()->create([
  209. 'brigadier_id' => $this->brigadierUser->id,
  210. 'order_status_id' => Order::STATUS_HANDED_OVER,
  211. ]);
  212. $response = $this->actingAs($this->brigadierUser)
  213. ->get(route('order.show', $order));
  214. $response->assertStatus(403);
  215. }
  216. // ==================== Edit ====================
  217. public function test_can_view_edit_order_form(): void
  218. {
  219. $order = Order::factory()->create();
  220. $response = $this->actingAs($this->managerUser)
  221. ->get(route('order.edit', $order));
  222. $response->assertStatus(200);
  223. $response->assertViewIs('orders.edit');
  224. }
  225. public function test_manager_can_edit_ready_date_in_order_edit_form(): void
  226. {
  227. $order = Order::factory()->create();
  228. $response = $this->actingAs($this->managerUser)
  229. ->get(route('order.edit', $order));
  230. $response->assertOk();
  231. $this->assertMatchesRegularExpression(
  232. '/<input[^>]*name="ready_date"(?:(?!disabled).)*>/s',
  233. $response->getContent()
  234. );
  235. }
  236. // ==================== Destroy ====================
  237. public function test_can_delete_order(): void
  238. {
  239. $order = Order::factory()->create();
  240. $orderId = $order->id;
  241. $response = $this->actingAs($this->adminUser)
  242. ->delete(route('order.destroy', $order));
  243. $response->assertRedirect(route('order.index'));
  244. // Order uses SoftDeletes, so check deleted_at is set
  245. $this->assertSoftDeleted('orders', ['id' => $orderId]);
  246. }
  247. public function test_deleting_order_removes_related_product_skus(): void
  248. {
  249. $order = Order::factory()->create();
  250. $product = Product::factory()->create();
  251. $sku = ProductSKU::factory()->create([
  252. 'order_id' => $order->id,
  253. 'product_id' => $product->id,
  254. ]);
  255. $this->actingAs($this->adminUser)
  256. ->delete(route('order.destroy', $order));
  257. // ProductSKU uses SoftDeletes, so check deleted_at is set
  258. $this->assertSoftDeleted('products_sku', ['id' => $sku->id]);
  259. }
  260. // ==================== Search ====================
  261. public function test_search_returns_matching_orders(): void
  262. {
  263. $order = Order::factory()->create([
  264. 'object_address' => 'ул. Уникальная Тестовая, д. 999',
  265. ]);
  266. $otherOrder = Order::factory()->create([
  267. 'object_address' => 'ул. Другая, д. 1',
  268. ]);
  269. $response = $this->actingAs($this->managerUser)
  270. ->get(route('order.index', ['s' => 'Уникальная Тестовая']));
  271. $response->assertStatus(200);
  272. $response->assertSee($order->object_address);
  273. $response->assertDontSee($otherOrder->object_address);
  274. }
  275. public function test_order_search_route_can_search_by_manager_name(): void
  276. {
  277. $manager = User::factory()->create([
  278. 'role' => Role::MANAGER,
  279. 'name' => 'Менеджер Поиска',
  280. ]);
  281. $matchedOrder = Order::factory()->create([
  282. 'user_id' => $manager->id,
  283. 'object_address' => 'ул. Найденная, д. 7',
  284. ]);
  285. $otherOrder = Order::factory()->create([
  286. 'object_address' => 'ул. Не должна попасть, д. 8',
  287. ]);
  288. $response = $this->actingAs($this->adminUser)
  289. ->getJson(route('order.search', ['s' => 'Менеджер Поиска']));
  290. $response->assertOk();
  291. $response->assertJsonPath((string) $matchedOrder->id, $matchedOrder->move_maf_name);
  292. $response->assertJsonMissing([$otherOrder->id => $otherOrder->move_maf_name]);
  293. }
  294. public function test_order_search_route_includes_site_name_and_excludes_current_order(): void
  295. {
  296. $district = District::factory()->create(['name' => 'Северный округ']);
  297. $area = Area::factory()->create([
  298. 'district_id' => $district->id,
  299. 'name' => 'Левобережный',
  300. ]);
  301. $currentOrder = Order::factory()->create([
  302. 'district_id' => $district->id,
  303. 'area_id' => $area->id,
  304. 'name' => 'Текущая площадка',
  305. 'object_address' => 'ул. Проверочная, д. 1',
  306. ]);
  307. $targetOrder = Order::factory()->create([
  308. 'district_id' => $district->id,
  309. 'area_id' => $area->id,
  310. 'name' => 'Площадка назначения',
  311. 'object_address' => 'ул. Проверочная, д. 2',
  312. ]);
  313. $response = $this->actingAs($this->adminUser)
  314. ->getJson(route('order.search', [
  315. 's' => 'Проверочная',
  316. 'current_order_id' => $currentOrder->id,
  317. ]));
  318. $response->assertOk();
  319. $response->assertJsonPath((string) $targetOrder->id, $targetOrder->move_maf_name);
  320. $response->assertJsonMissing([(string) $currentOrder->id => $currentOrder->move_maf_name]);
  321. }
  322. // ==================== MAF Operations ====================
  323. public function test_get_maf_to_order_assigns_available_maf(): void
  324. {
  325. $product = Product::factory()->create();
  326. $order = Order::factory()->create();
  327. $productSku = ProductSKU::factory()->create([
  328. 'order_id' => $order->id,
  329. 'product_id' => $product->id,
  330. 'maf_order_id' => null,
  331. 'status' => 'требуется',
  332. ]);
  333. $mafOrder = MafOrder::factory()->create([
  334. 'product_id' => $product->id,
  335. 'in_stock' => 5,
  336. ]);
  337. // This route requires admin role
  338. $response = $this->actingAs($this->adminUser)
  339. ->get(route('order.get-maf', $order));
  340. $response->assertRedirect(route('order.show', $order));
  341. $response->assertSessionHas('success', function ($messages) {
  342. return str_contains(implode(' ', (array) $messages), 'Привязано МАФ: 1.');
  343. });
  344. $productSku->refresh();
  345. $this->assertEquals($mafOrder->id, $productSku->maf_order_id);
  346. $this->assertEquals('отгружен', $productSku->status);
  347. $mafOrder->refresh();
  348. $this->assertEquals(4, $mafOrder->in_stock);
  349. }
  350. public function test_get_maf_to_order_shows_error_when_not_enough_stock(): void
  351. {
  352. $product = Product::factory()->create();
  353. $order = Order::factory()->create();
  354. $productSku = ProductSKU::factory()->create([
  355. 'order_id' => $order->id,
  356. 'product_id' => $product->id,
  357. 'maf_order_id' => null,
  358. 'status' => 'требуется',
  359. ]);
  360. $response = $this->actingAs($this->adminUser)
  361. ->get(route('order.get-maf', $order));
  362. $response->assertRedirect(route('order.show', $order));
  363. $response->assertSessionHas('danger', function ($messages) {
  364. return str_contains(implode(' ', (array) $messages), 'Не удалось привязать 1 шт. МАФ');
  365. });
  366. $productSku->refresh();
  367. $this->assertNull($productSku->maf_order_id);
  368. $this->assertEquals('требуется', $productSku->status);
  369. }
  370. public function test_revert_maf_returns_maf_to_stock(): void
  371. {
  372. $product = Product::factory()->create();
  373. $order = Order::factory()->create();
  374. $mafOrder = MafOrder::factory()->create([
  375. 'product_id' => $product->id,
  376. 'in_stock' => 3,
  377. ]);
  378. $productSku = ProductSKU::factory()->create([
  379. 'order_id' => $order->id,
  380. 'product_id' => $product->id,
  381. 'maf_order_id' => $mafOrder->id,
  382. 'status' => 'отгружен',
  383. ]);
  384. // This route requires admin role
  385. $response = $this->actingAs($this->adminUser)
  386. ->get(route('order.revert-maf', $order));
  387. $response->assertRedirect(route('order.show', $order));
  388. $response->assertSessionHas('success', function ($messages) {
  389. return str_contains(implode(' ', (array) $messages), 'Отвязано МАФ: 1.');
  390. });
  391. $productSku->refresh();
  392. $this->assertNull($productSku->maf_order_id);
  393. $this->assertEquals('требуется', $productSku->status);
  394. $mafOrder->refresh();
  395. $this->assertEquals(4, $mafOrder->in_stock);
  396. }
  397. public function test_revert_maf_shows_error_when_nothing_is_attached(): void
  398. {
  399. $order = Order::factory()->create();
  400. ProductSKU::factory()->create([
  401. 'order_id' => $order->id,
  402. 'maf_order_id' => null,
  403. 'status' => 'требуется',
  404. ]);
  405. $response = $this->actingAs($this->adminUser)
  406. ->get(route('order.revert-maf', $order));
  407. $response->assertRedirect(route('order.show', $order));
  408. $response->assertSessionHas('danger', function ($messages) {
  409. return str_contains(implode(' ', (array) $messages), 'Нет МАФ для отвязки');
  410. });
  411. }
  412. public function test_store_order_cannot_remove_attached_maf_from_order_list(): void
  413. {
  414. $attachedProduct = Product::factory()->create();
  415. $otherProduct = Product::factory()->create();
  416. $order = Order::factory()->create([
  417. 'order_status_id' => Order::STATUS_NEW,
  418. ]);
  419. $mafOrder = MafOrder::factory()->create([
  420. 'product_id' => $attachedProduct->id,
  421. 'in_stock' => 1,
  422. ]);
  423. $attachedSku = ProductSKU::factory()->create([
  424. 'order_id' => $order->id,
  425. 'product_id' => $attachedProduct->id,
  426. 'maf_order_id' => $mafOrder->id,
  427. 'status' => 'отгружен',
  428. ]);
  429. ProductSKU::factory()->create([
  430. 'order_id' => $order->id,
  431. 'product_id' => $otherProduct->id,
  432. 'maf_order_id' => null,
  433. 'status' => 'требуется',
  434. ]);
  435. $response = $this->actingAs($this->adminUser)
  436. ->post(route('order.store'), [
  437. 'id' => $order->id,
  438. 'products' => [$otherProduct->id],
  439. 'quantity' => [1],
  440. ]);
  441. $response->assertRedirect();
  442. $response->assertSessionHas('danger', function ($messages) {
  443. return str_contains(implode(' ', (array) $messages), 'Нельзя удалить привязанные МАФ');
  444. });
  445. $this->assertDatabaseHas('products_sku', [
  446. 'id' => $attachedSku->id,
  447. 'maf_order_id' => $mafOrder->id,
  448. 'deleted_at' => null,
  449. ]);
  450. }
  451. public function test_store_order_cannot_add_new_positions_when_has_attached_maf(): void
  452. {
  453. $attachedProduct = Product::factory()->create();
  454. $existingProduct = Product::factory()->create();
  455. $newProduct = Product::factory()->create();
  456. $order = Order::factory()->create([
  457. 'order_status_id' => Order::STATUS_NEW,
  458. ]);
  459. $mafOrder = MafOrder::factory()->create([
  460. 'product_id' => $attachedProduct->id,
  461. 'in_stock' => 1,
  462. ]);
  463. ProductSKU::factory()->create([
  464. 'order_id' => $order->id,
  465. 'product_id' => $attachedProduct->id,
  466. 'maf_order_id' => $mafOrder->id,
  467. 'status' => 'отгружен',
  468. ]);
  469. ProductSKU::factory()->create([
  470. 'order_id' => $order->id,
  471. 'product_id' => $existingProduct->id,
  472. 'maf_order_id' => null,
  473. 'status' => 'требуется',
  474. ]);
  475. $response = $this->actingAs($this->adminUser)
  476. ->post(route('order.store'), [
  477. 'id' => $order->id,
  478. 'products' => [$attachedProduct->id, $existingProduct->id, $newProduct->id],
  479. 'quantity' => [1, 1, 1],
  480. ]);
  481. $response->assertRedirect();
  482. $response->assertSessionHas('danger', function ($messages) {
  483. return str_contains(implode(' ', (array) $messages), 'Нельзя добавлять новые позиции МАФ');
  484. });
  485. $this->assertDatabaseMissing('products_sku', [
  486. 'order_id' => $order->id,
  487. 'product_id' => $newProduct->id,
  488. 'deleted_at' => null,
  489. ]);
  490. }
  491. public function test_move_maf_transfers_sku_to_another_order(): void
  492. {
  493. $product = Product::factory()->create();
  494. $order1 = Order::factory()->create();
  495. $order2 = Order::factory()->create();
  496. $productSku = ProductSKU::factory()->create([
  497. 'order_id' => $order1->id,
  498. 'product_id' => $product->id,
  499. ]);
  500. // This route requires admin role
  501. $response = $this->actingAs($this->adminUser)
  502. ->post(route('order.move-maf'), [
  503. 'new_order_id' => $order2->id,
  504. 'ids' => json_encode([$productSku->id]),
  505. ]);
  506. $response->assertStatus(200);
  507. $response->assertJson(['success' => true]);
  508. $productSku->refresh();
  509. $this->assertEquals($order2->id, $productSku->order_id);
  510. }
  511. // ==================== Photo Management ====================
  512. public function test_can_upload_photo_to_order(): void
  513. {
  514. Storage::fake('public');
  515. $order = Order::factory()->create();
  516. // Use create() instead of image() to avoid GD extension requirement
  517. $photo = UploadedFile::fake()->create('photo.jpg', 100, 'image/jpeg');
  518. $response = $this->actingAs($this->managerUser)
  519. ->post(route('order.upload-photo', $order), [
  520. 'photo' => [$photo],
  521. ]);
  522. $response->assertRedirect(route('order.show', $order));
  523. $this->assertCount(1, $order->fresh()->photos);
  524. }
  525. public function test_can_delete_photo_from_order(): void
  526. {
  527. Storage::fake('public');
  528. $order = Order::factory()->create();
  529. $file = File::factory()->create();
  530. $order->photos()->attach($file);
  531. // This route requires admin role
  532. $response = $this->actingAs($this->adminUser)
  533. ->delete(route('order.delete-photo', [$order, $file]));
  534. $response->assertRedirect(route('order.show', $order));
  535. $this->assertCount(0, $order->fresh()->photos);
  536. }
  537. public function test_can_delete_all_photos_from_order(): void
  538. {
  539. Storage::fake('public');
  540. $order = Order::factory()->create();
  541. $files = File::factory()->count(3)->create();
  542. $order->photos()->attach($files->pluck('id'));
  543. // This route requires admin role
  544. $response = $this->actingAs($this->adminUser)
  545. ->delete(route('order.delete-all-photos', $order));
  546. $response->assertRedirect(route('order.show', $order));
  547. $this->assertCount(0, $order->fresh()->photos);
  548. }
  549. // ==================== Document Management ====================
  550. public function test_can_upload_document_to_order(): void
  551. {
  552. Storage::fake('public');
  553. $order = Order::factory()->create();
  554. $document = UploadedFile::fake()->create('document.pdf', 100);
  555. $response = $this->actingAs($this->managerUser)
  556. ->post(route('order.upload-document', $order), [
  557. 'document' => [$document],
  558. ]);
  559. $response->assertRedirect(route('order.show', $order));
  560. $this->assertCount(1, $order->fresh()->documents);
  561. }
  562. public function test_upload_document_preserves_unicode_and_quotes_filename(): void
  563. {
  564. Storage::fake('public');
  565. $order = Order::factory()->create();
  566. $filename = "Документ «смета» 'финал' \"v2\".pdf";
  567. $document = UploadedFile::fake()->create($filename, 100, 'application/pdf');
  568. $response = $this->actingAs($this->managerUser)
  569. ->post(route('order.upload-document', $order), [
  570. 'document' => [$document],
  571. ]);
  572. $response->assertRedirect(route('order.show', $order));
  573. $saved = $order->fresh()->documents->first();
  574. $this->assertNotNull($saved);
  575. $this->assertEquals($filename, $saved->original_name);
  576. $this->assertEquals('orders/' . $order->id . '/document/' . $filename, $saved->path);
  577. Storage::disk('public')->assertExists($saved->path);
  578. }
  579. public function test_upload_statement_preserves_unicode_and_quotes_filename(): void
  580. {
  581. Storage::fake('public');
  582. $order = Order::factory()->create();
  583. $filename = "Акт «приемки» 'этап 1' \"финал\".pdf";
  584. $statement = UploadedFile::fake()->create($filename, 100, 'application/pdf');
  585. $response = $this->actingAs($this->managerUser)
  586. ->post(route('order.upload-statement', $order), [
  587. 'statement' => [$statement],
  588. ]);
  589. $response->assertRedirect(route('order.show', $order));
  590. $saved = $order->fresh()->statements->first();
  591. $this->assertNotNull($saved);
  592. $this->assertEquals($filename, $saved->original_name);
  593. $this->assertEquals('orders/' . $order->id . '/statement/' . $filename, $saved->path);
  594. Storage::disk('public')->assertExists($saved->path);
  595. }
  596. public function test_upload_document_limits_to_5_files(): void
  597. {
  598. Storage::fake('public');
  599. $order = Order::factory()->create();
  600. $documents = [];
  601. for ($i = 0; $i < 7; $i++) {
  602. $documents[] = UploadedFile::fake()->create("document{$i}.pdf", 100);
  603. }
  604. $this->actingAs($this->managerUser)
  605. ->post(route('order.upload-document', $order), [
  606. 'document' => $documents,
  607. ]);
  608. $this->assertCount(5, $order->fresh()->documents);
  609. }
  610. public function test_can_upload_webp_photo(): void
  611. {
  612. Storage::fake('public');
  613. $order = Order::factory()->create();
  614. $photo = UploadedFile::fake()->create('photo.webp', 100, 'image/webp');
  615. $response = $this->actingAs($this->managerUser)
  616. ->post(route('order.upload-photo', $order), [
  617. 'photo' => [$photo],
  618. ]);
  619. $response->assertRedirect();
  620. $saved = $order->fresh()->photos->first();
  621. $this->assertNotNull($saved);
  622. $this->assertSame('photo.webp', $saved->original_name);
  623. }
  624. // ==================== Generation ====================
  625. public function test_generate_installation_pack_is_allowed_for_any_status_when_data_is_valid(): void
  626. {
  627. Bus::fake();
  628. $product = Product::factory()->create();
  629. $mafOrder = MafOrder::factory()->create(['product_id' => $product->id]);
  630. $order = Order::factory()->create([
  631. 'order_status_id' => Order::STATUS_NEW,
  632. ]);
  633. ProductSKU::factory()->create([
  634. 'order_id' => $order->id,
  635. 'product_id' => $product->id,
  636. 'maf_order_id' => $mafOrder->id,
  637. ]);
  638. $response = $this->actingAs($this->managerUser)
  639. ->get(route('order.generate-installation-pack', $order));
  640. $response->assertRedirect(route('order.show', $order));
  641. $response->assertSessionHas('success');
  642. Bus::assertDispatched(GenerateInstallationPack::class);
  643. }
  644. public function test_generate_installation_pack_still_requires_connected_maf(): void
  645. {
  646. Bus::fake();
  647. $product = Product::factory()->create();
  648. $order = Order::factory()->create([
  649. 'order_status_id' => Order::STATUS_IN_MOUNT,
  650. ]);
  651. ProductSKU::factory()->create([
  652. 'order_id' => $order->id,
  653. 'product_id' => $product->id,
  654. 'maf_order_id' => null,
  655. ]);
  656. $response = $this->actingAs($this->managerUser)
  657. ->get(route('order.generate-installation-pack', $order));
  658. $response->assertRedirect(route('order.show', $order));
  659. $response->assertSessionHas('danger');
  660. Bus::assertNotDispatched(GenerateInstallationPack::class);
  661. }
  662. // ==================== Export ====================
  663. public function test_can_export_orders(): void
  664. {
  665. Order::factory()->count(3)->create();
  666. // This route requires admin role
  667. $response = $this->actingAs($this->adminUser)
  668. ->post(route('order.export'));
  669. $response->assertRedirect(route('order.index'));
  670. $response->assertSessionHas('success');
  671. }
  672. public function test_can_export_single_order(): void
  673. {
  674. $order = Order::factory()->create();
  675. // This route requires admin role
  676. $response = $this->actingAs($this->adminUser)
  677. ->post(route('order.export-one', $order));
  678. $response->assertRedirect(route('order.show', $order));
  679. $response->assertSessionHas('success');
  680. }
  681. }