| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135 |
- <?php
- namespace Tests\Feature;
- use App\Jobs\GenerateInstallationPack;
- use App\Jobs\GenerateTtnPack;
- use App\Models\Dictionary\Area;
- use App\Models\Dictionary\District;
- use App\Models\File;
- use App\Models\MafOrder;
- use App\Models\ObjectType;
- use App\Models\Order;
- use App\Models\OrderStatus;
- use App\Models\Product;
- use App\Models\ProductSKU;
- use App\Models\Role;
- use App\Models\Setting;
- use App\Models\Ttn;
- use App\Models\User;
- use Illuminate\Foundation\Testing\RefreshDatabase;
- use Illuminate\Http\UploadedFile;
- use Illuminate\Support\Facades\Bus;
- use Illuminate\Support\Facades\Storage;
- use Tests\TestCase;
- class OrderControllerTest extends TestCase
- {
- use RefreshDatabase;
- protected $seed = true;
- private User $adminUser;
- private User $managerUser;
- private User $brigadierUser;
- protected function setUp(): void
- {
- parent::setUp();
- $this->adminUser = User::factory()->create(['role' => Role::ADMIN]);
- $this->managerUser = User::factory()->create(['role' => Role::MANAGER]);
- $this->brigadierUser = User::factory()->create(['role' => Role::BRIGADIER]);
- }
- // ==================== Authentication ====================
- public function test_guest_cannot_access_orders_index(): void
- {
- $response = $this->get(route('order.index'));
- $response->assertRedirect(route('login'));
- }
- public function test_authenticated_user_can_access_orders_index(): void
- {
- $response = $this->actingAs($this->managerUser)
- ->get(route('order.index'));
- $response->assertStatus(200);
- $response->assertViewIs('orders.index');
- }
- // ==================== Index ====================
- public function test_orders_index_displays_orders(): void
- {
- $order = Order::factory()->create([
- 'object_address' => 'ул. Проверочная, д. 101',
- ]);
- $response = $this->actingAs($this->managerUser)
- ->get(route('order.index'));
- $response->assertStatus(200);
- $response->assertSee($order->object_address);
- }
- public function test_orders_index_displays_products_total_column_value(): void
- {
- $order = Order::factory()->create([
- 'object_address' => 'ул. Колоночная, д. 10',
- ]);
- $product = Product::factory()->create();
- ProductSKU::factory()->count(3)->create([
- 'order_id' => $order->id,
- 'product_id' => $product->id,
- ]);
- $response = $this->actingAs($this->managerUser)
- ->get(route('order.index'));
- $response->assertOk();
- $response->assertSee('Всего МАФ');
- $response->assertSee('ул. Колоночная, д. 10');
- $response->assertViewHas('orders', static function ($orders) use ($order) {
- return (int) $orders->firstWhere('id', $order->id)?->products_total === 3;
- });
- }
- public function test_orders_index_displays_filtered_footer_stats(): void
- {
- $district = District::factory()->create(['shortname' => 'ЦАО']);
- $area = Area::factory()->create(['district_id' => $district->id, 'name' => 'Тверской']);
- $matchingOrder = Order::factory()->create([
- 'district_id' => $district->id,
- 'area_id' => $area->id,
- 'object_address' => 'ул. Итоговая, д. 1',
- ]);
- $otherOrder = Order::factory()->create([
- 'object_address' => 'ул. Неучтенная, д. 2',
- ]);
- $product = Product::factory()->create();
- ProductSKU::factory()->count(2)->create([
- 'order_id' => $matchingOrder->id,
- 'product_id' => $product->id,
- ]);
- ProductSKU::factory()->count(5)->create([
- 'order_id' => $otherOrder->id,
- 'product_id' => $product->id,
- ]);
- $response = $this->actingAs($this->managerUser)
- ->get(route('order.index', ['s' => 'Итоговая']));
- $response->assertOk();
- $response->assertSee('Адресов: 1', false);
- $response->assertSee('МАФ: 2', false);
- $response->assertDontSee('МАФ: 7', false);
- }
- public function test_brigadier_sees_only_assigned_orders(): void
- {
- $assignedOrder = Order::factory()->create([
- 'brigadier_id' => $this->brigadierUser->id,
- 'object_address' => 'ул. Бригадирская, д. 10',
- ]);
- $otherOrder = Order::factory()->create([
- 'brigadier_id' => User::factory()->create(['role' => Role::BRIGADIER])->id,
- 'object_address' => 'ул. Чужая, д. 20',
- ]);
- $response = $this->actingAs($this->brigadierUser)
- ->get(route('order.index'));
- $response->assertStatus(200);
- $response->assertSee($assignedOrder->object_address);
- $response->assertDontSee($otherOrder->object_address);
- }
- public function test_brigadier_does_not_see_handed_over_orders_in_index(): void
- {
- $visibleOrder = Order::factory()->create([
- 'brigadier_id' => $this->brigadierUser->id,
- 'order_status_id' => Order::STATUS_IN_MOUNT,
- 'object_address' => 'ул. Видимая, д. 11',
- ]);
- $hiddenOrder = Order::factory()->create([
- 'brigadier_id' => $this->brigadierUser->id,
- 'order_status_id' => Order::STATUS_HANDED_OVER,
- 'object_address' => 'ул. Сданная, д. 12',
- ]);
- $response = $this->actingAs($this->brigadierUser)
- ->get(route('order.index'));
- $response->assertStatus(200);
- $response->assertSee($visibleOrder->object_address);
- $response->assertDontSee($hiddenOrder->object_address);
- }
- // ==================== Create ====================
- public function test_can_view_create_order_form(): void
- {
- $response = $this->actingAs($this->adminUser)
- ->get(route('order.create'));
- $response->assertStatus(200);
- $response->assertViewIs('orders.edit');
- }
- // ==================== Store ====================
- public function test_can_create_new_order(): void
- {
- $district = District::factory()->create();
- $area = Area::factory()->create();
- $objectType = ObjectType::factory()->create();
- $response = $this->actingAs($this->managerUser)
- ->post(route('order.store'), [
- 'name' => 'Тестовый заказ',
- 'user_id' => $this->managerUser->id,
- 'district_id' => $district->id,
- 'area_id' => $area->id,
- 'object_address' => 'ул. Тестовая, д. 1',
- 'object_type_id' => $objectType->id,
- 'comment' => 'Тестовый комментарий',
- ]);
- $response->assertRedirect();
- $this->assertDatabaseHas('orders', [
- 'object_address' => 'ул. Тестовая, д. 1',
- 'order_status_id' => Order::STATUS_NEW,
- ]);
- }
- public function test_order_show_uses_nav_context_for_back_url(): void
- {
- $order = Order::factory()->create();
- $indexResponse = $this->actingAs($this->managerUser)
- ->get(route('order.index', [
- 'filters' => ['object_address' => 'Test address'],
- ]));
- $nav = $indexResponse->viewData('nav');
- $response = $this->actingAs($this->managerUser)
- ->get(route('order.show', [
- 'order' => $order,
- 'nav' => $nav,
- ]));
- $response->assertOk();
- $response->assertViewHas('nav', $nav);
- $response->assertViewHas('back_url', function (string $backUrl): bool {
- if (!str_starts_with($backUrl, route('order.index'))) {
- return false;
- }
- $query = parse_url($backUrl, PHP_URL_QUERY);
- parse_str((string) $query, $params);
- return ($params['filters']['object_address'] ?? null) === 'Test address';
- });
- }
- public function test_order_index_starts_new_navigation_context(): void
- {
- $response = $this->actingAs($this->managerUser)
- ->withSession([
- 'navigation' => [
- 'existing-card-nav' => [
- 'updated_at' => time(),
- 'stack' => [
- route('order.show', Order::factory()->create()),
- route('reclamations.index'),
- ],
- ],
- ],
- ])
- ->get(route('order.index', ['nav' => 'existing-card-nav']));
- $response->assertOk();
- $response->assertViewHas('nav', function (string $nav): bool {
- return $nav !== 'existing-card-nav';
- });
- }
- public function test_order_store_redirects_to_show_with_nav_token(): void
- {
- $district = District::factory()->create();
- $area = Area::factory()->create();
- $objectType = ObjectType::factory()->create();
- $response = $this->actingAs($this->managerUser)
- ->withSession([
- 'navigation' => [
- 'order-nav-token' => [
- 'updated_at' => time(),
- 'stack' => [
- route('order.index'),
- ],
- ],
- ],
- ])
- ->post(route('order.store'), [
- 'nav' => 'order-nav-token',
- 'name' => 'Тестовый заказ',
- 'user_id' => $this->managerUser->id,
- 'district_id' => $district->id,
- 'area_id' => $area->id,
- 'object_address' => 'ул. Навигационная, д. 5',
- 'object_type_id' => $objectType->id,
- 'comment' => 'Тестовый комментарий',
- ]);
- $createdOrder = Order::query()->where('object_address', 'ул. Навигационная, д. 5')->firstOrFail();
- $response->assertRedirect(route('order.show', [
- 'order' => $createdOrder,
- 'nav' => 'order-nav-token',
- ]));
- }
- public function test_creating_order_sets_tg_group_name(): void
- {
- $district = District::factory()->create(['shortname' => 'ЦАО']);
- $area = Area::factory()->create(['name' => 'Тверской']);
- $objectType = ObjectType::factory()->create();
- $this->actingAs($this->managerUser)
- ->post(route('order.store'), [
- 'name' => 'Тестовый заказ',
- 'user_id' => $this->managerUser->id,
- 'district_id' => $district->id,
- 'area_id' => $area->id,
- 'object_address' => 'ул. Пушкина',
- 'object_type_id' => $objectType->id,
- ]);
- $order = Order::where('object_address', 'ул. Пушкина')->first();
- $this->assertStringContainsString('ЦАО', $order->tg_group_name);
- $this->assertStringContainsString('Тверской', $order->tg_group_name);
- }
- public function test_can_update_existing_order(): void
- {
- $order = Order::factory()->create([
- 'object_address' => 'Старый адрес',
- ]);
- $response = $this->actingAs($this->managerUser)
- ->post(route('order.store'), [
- 'id' => $order->id,
- 'name' => $order->name,
- 'user_id' => $order->user_id,
- 'district_id' => $order->district_id,
- 'area_id' => $order->area_id,
- 'object_address' => 'Новый адрес',
- 'object_type_id' => $order->object_type_id,
- ]);
- $response->assertRedirect();
- $this->assertDatabaseHas('orders', [
- 'id' => $order->id,
- 'object_address' => 'Новый адрес',
- ]);
- }
- public function test_cannot_set_in_mount_status_when_order_has_unlinked_maf(): void
- {
- $product = Product::factory()->create();
- $order = Order::factory()->readyToMount()->withBrigadier($this->brigadierUser)->create([
- 'installation_date' => '2026-04-25',
- ]);
- ProductSKU::factory()->create([
- 'order_id' => $order->id,
- 'product_id' => $product->id,
- 'maf_order_id' => null,
- ]);
- $response = $this->actingAs($this->adminUser)
- ->post(route('order.update'), [
- 'id' => $order->id,
- 'order_status_id' => Order::STATUS_IN_MOUNT,
- ]);
- $response->assertRedirect();
- $response->assertSessionHas('danger', function ($messages) {
- return in_array('МАФ не привязан к заказу', (array) $messages, true);
- });
- $this->assertDatabaseHas('orders', [
- 'id' => $order->id,
- 'order_status_id' => Order::STATUS_READY_TO_MOUNT,
- ]);
- }
- public function test_ajax_cannot_set_in_mount_status_when_maf_order_number_is_empty(): void
- {
- $product = Product::factory()->create();
- $order = Order::factory()->readyToMount()->withBrigadier($this->brigadierUser)->create([
- 'installation_date' => '2026-04-25',
- ]);
- $mafOrder = MafOrder::factory()->create([
- 'product_id' => $product->id,
- 'order_number' => '',
- ]);
- ProductSKU::factory()->create([
- 'order_id' => $order->id,
- 'product_id' => $product->id,
- 'maf_order_id' => $mafOrder->id,
- ]);
- $response = $this->actingAs($this->adminUser)
- ->withHeader('X-Requested-With', 'XMLHttpRequest')
- ->post(route('order.update'), [
- 'id' => $order->id,
- 'order_status_id' => Order::STATUS_IN_MOUNT,
- ]);
- $response->assertStatus(422);
- $response->assertJson([
- 'message' => 'МАФ не привязан к заказу',
- ]);
- $this->assertDatabaseHas('orders', [
- 'id' => $order->id,
- 'order_status_id' => Order::STATUS_READY_TO_MOUNT,
- ]);
- }
- public function test_can_set_in_mount_status_when_all_mafs_are_linked_to_order_numbers(): void
- {
- $product = Product::factory()->create();
- $order = Order::factory()->readyToMount()->withBrigadier($this->brigadierUser)->create([
- 'installation_date' => '2026-04-25',
- ]);
- $mafOrder = MafOrder::factory()->create([
- 'product_id' => $product->id,
- 'order_number' => 'MO-7788',
- ]);
- ProductSKU::factory()->create([
- 'order_id' => $order->id,
- 'product_id' => $product->id,
- 'maf_order_id' => $mafOrder->id,
- ]);
- $response = $this->actingAs($this->adminUser)
- ->post(route('order.update'), [
- 'id' => $order->id,
- 'order_status_id' => Order::STATUS_IN_MOUNT,
- ]);
- $response->assertRedirect();
- $this->assertDatabaseHas('orders', [
- 'id' => $order->id,
- 'order_status_id' => Order::STATUS_IN_MOUNT,
- ]);
- }
- public function test_manager_can_update_ready_date(): void
- {
- $order = Order::factory()->create([
- 'ready_date' => '2026-04-01',
- ]);
- $response = $this->actingAs($this->managerUser)
- ->post(route('order.store'), [
- 'id' => $order->id,
- 'ready_date' => '2026-04-25',
- ]);
- $response->assertRedirect();
- $this->assertDatabaseHas('orders', [
- 'id' => $order->id,
- 'ready_date' => '2026-04-25',
- ]);
- }
- // ==================== Show ====================
- public function test_can_view_order_details(): void
- {
- $order = Order::factory()->create([
- 'object_address' => 'ул. Детальная, д. 5',
- ]);
- $response = $this->actingAs($this->managerUser)
- ->get(route('order.show', $order));
- $response->assertStatus(200);
- $response->assertViewIs('orders.show');
- $response->assertSee($order->object_address);
- }
- public function test_order_details_show_area_and_district(): void
- {
- $district = District::factory()->create(['name' => 'Центральный округ']);
- $area = Area::factory()->create([
- 'district_id' => $district->id,
- 'name' => 'Тверской район',
- ]);
- $order = Order::factory()->create([
- 'district_id' => $district->id,
- 'area_id' => $area->id,
- ]);
- $response = $this->actingAs($this->managerUser)
- ->get(route('order.show', $order));
- $response->assertOk();
- $response->assertSee('Центральный округ');
- $response->assertSee('Тверской район');
- }
- public function test_brigadier_cannot_view_handed_over_order_details(): void
- {
- $order = Order::factory()->create([
- 'brigadier_id' => $this->brigadierUser->id,
- 'order_status_id' => Order::STATUS_HANDED_OVER,
- ]);
- $response = $this->actingAs($this->brigadierUser)
- ->get(route('order.show', $order));
- $response->assertStatus(403);
- }
- // ==================== Edit ====================
- public function test_can_view_edit_order_form(): void
- {
- $order = Order::factory()->create();
- $response = $this->actingAs($this->managerUser)
- ->get(route('order.edit', $order));
- $response->assertStatus(200);
- $response->assertViewIs('orders.edit');
- }
- public function test_manager_can_edit_ready_date_in_order_edit_form(): void
- {
- $order = Order::factory()->create();
- $response = $this->actingAs($this->managerUser)
- ->get(route('order.edit', $order));
- $response->assertOk();
- $this->assertMatchesRegularExpression(
- '/<input[^>]*name="ready_date"(?:(?!disabled).)*>/s',
- $response->getContent()
- );
- }
- // ==================== Destroy ====================
- public function test_can_delete_order(): void
- {
- $order = Order::factory()->create();
- $orderId = $order->id;
- $response = $this->actingAs($this->adminUser)
- ->delete(route('order.destroy', $order));
- $response->assertRedirect(route('order.index'));
- // Order uses SoftDeletes, so check deleted_at is set
- $this->assertSoftDeleted('orders', ['id' => $orderId]);
- }
- public function test_deleting_order_removes_related_product_skus(): void
- {
- $order = Order::factory()->create();
- $product = Product::factory()->create();
- $sku = ProductSKU::factory()->create([
- 'order_id' => $order->id,
- 'product_id' => $product->id,
- ]);
- $this->actingAs($this->adminUser)
- ->delete(route('order.destroy', $order));
- // ProductSKU uses SoftDeletes, so check deleted_at is set
- $this->assertSoftDeleted('products_sku', ['id' => $sku->id]);
- }
- // ==================== Search ====================
- public function test_search_returns_matching_orders(): void
- {
- $order = Order::factory()->create([
- 'object_address' => 'ул. Уникальная Тестовая, д. 999',
- ]);
- $otherOrder = Order::factory()->create([
- 'object_address' => 'ул. Другая, д. 1',
- ]);
- $response = $this->actingAs($this->managerUser)
- ->get(route('order.index', ['s' => 'Уникальная Тестовая']));
- $response->assertStatus(200);
- $response->assertSee($order->object_address);
- $response->assertDontSee($otherOrder->object_address);
- }
- public function test_order_search_route_can_search_by_manager_name(): void
- {
- $manager = User::factory()->create([
- 'role' => Role::MANAGER,
- 'name' => 'Менеджер Поиска',
- ]);
- $matchedOrder = Order::factory()->create([
- 'user_id' => $manager->id,
- 'object_address' => 'ул. Найденная, д. 7',
- ]);
- $otherOrder = Order::factory()->create([
- 'object_address' => 'ул. Не должна попасть, д. 8',
- ]);
- $response = $this->actingAs($this->adminUser)
- ->getJson(route('order.search', ['s' => 'Менеджер Поиска']));
- $response->assertOk();
- $response->assertJsonPath((string) $matchedOrder->id, $matchedOrder->move_maf_name);
- $response->assertJsonMissing([$otherOrder->id => $otherOrder->move_maf_name]);
- }
- public function test_order_search_route_includes_site_name_and_excludes_current_order(): void
- {
- $district = District::factory()->create(['name' => 'Северный округ']);
- $area = Area::factory()->create([
- 'district_id' => $district->id,
- 'name' => 'Левобережный',
- ]);
- $currentOrder = Order::factory()->create([
- 'district_id' => $district->id,
- 'area_id' => $area->id,
- 'name' => 'Текущая площадка',
- 'object_address' => 'ул. Проверочная, д. 1',
- ]);
- $targetOrder = Order::factory()->create([
- 'district_id' => $district->id,
- 'area_id' => $area->id,
- 'name' => 'Площадка назначения',
- 'object_address' => 'ул. Проверочная, д. 2',
- ]);
- $response = $this->actingAs($this->adminUser)
- ->getJson(route('order.search', [
- 's' => 'Проверочная',
- 'current_order_id' => $currentOrder->id,
- ]));
- $response->assertOk();
- $response->assertJsonPath((string) $targetOrder->id, $targetOrder->move_maf_name);
- $response->assertJsonMissing([(string) $currentOrder->id => $currentOrder->move_maf_name]);
- }
- // ==================== MAF Operations ====================
- public function test_get_maf_to_order_assigns_available_maf(): void
- {
- $product = Product::factory()->create();
- $order = Order::factory()->create();
- $productSku = ProductSKU::factory()->create([
- 'order_id' => $order->id,
- 'product_id' => $product->id,
- 'maf_order_id' => null,
- 'status' => 'требуется',
- ]);
- $mafOrder = MafOrder::factory()->create([
- 'product_id' => $product->id,
- 'in_stock' => 5,
- ]);
- // This route requires admin role
- $response = $this->actingAs($this->adminUser)
- ->get(route('order.get-maf', $order));
- $response->assertRedirect(route('order.show', $order));
- $response->assertSessionHas('success', function ($messages) {
- return str_contains(implode(' ', (array) $messages), 'Привязано МАФ: 1.');
- });
- $productSku->refresh();
- $this->assertEquals($mafOrder->id, $productSku->maf_order_id);
- $this->assertEquals('отгружен', $productSku->status);
- $mafOrder->refresh();
- $this->assertEquals(4, $mafOrder->in_stock);
- }
- public function test_get_maf_to_order_shows_error_when_not_enough_stock(): void
- {
- $product = Product::factory()->create();
- $order = Order::factory()->create();
- $productSku = ProductSKU::factory()->create([
- 'order_id' => $order->id,
- 'product_id' => $product->id,
- 'maf_order_id' => null,
- 'status' => 'требуется',
- ]);
- $response = $this->actingAs($this->adminUser)
- ->get(route('order.get-maf', $order));
- $response->assertRedirect(route('order.show', $order));
- $response->assertSessionHas('danger', function ($messages) {
- return str_contains(implode(' ', (array) $messages), 'Не удалось привязать 1 шт. МАФ');
- });
- $productSku->refresh();
- $this->assertNull($productSku->maf_order_id);
- $this->assertEquals('требуется', $productSku->status);
- }
- public function test_revert_maf_returns_maf_to_stock(): void
- {
- $product = Product::factory()->create();
- $order = Order::factory()->create();
- $mafOrder = MafOrder::factory()->create([
- 'product_id' => $product->id,
- 'in_stock' => 3,
- ]);
- $productSku = ProductSKU::factory()->create([
- 'order_id' => $order->id,
- 'product_id' => $product->id,
- 'maf_order_id' => $mafOrder->id,
- 'status' => 'отгружен',
- ]);
- // This route requires admin role
- $response = $this->actingAs($this->adminUser)
- ->get(route('order.revert-maf', $order));
- $response->assertRedirect(route('order.show', $order));
- $response->assertSessionHas('success', function ($messages) {
- return str_contains(implode(' ', (array) $messages), 'Отвязано МАФ: 1.');
- });
- $productSku->refresh();
- $this->assertNull($productSku->maf_order_id);
- $this->assertEquals('требуется', $productSku->status);
- $mafOrder->refresh();
- $this->assertEquals(4, $mafOrder->in_stock);
- }
- public function test_revert_maf_shows_error_when_nothing_is_attached(): void
- {
- $order = Order::factory()->create();
- ProductSKU::factory()->create([
- 'order_id' => $order->id,
- 'maf_order_id' => null,
- 'status' => 'требуется',
- ]);
- $response = $this->actingAs($this->adminUser)
- ->get(route('order.revert-maf', $order));
- $response->assertRedirect(route('order.show', $order));
- $response->assertSessionHas('danger', function ($messages) {
- return str_contains(implode(' ', (array) $messages), 'Нет МАФ для отвязки');
- });
- }
- public function test_store_order_cannot_remove_attached_maf_from_order_list(): void
- {
- $attachedProduct = Product::factory()->create();
- $otherProduct = Product::factory()->create();
- $order = Order::factory()->create([
- 'order_status_id' => Order::STATUS_NEW,
- ]);
- $mafOrder = MafOrder::factory()->create([
- 'product_id' => $attachedProduct->id,
- 'in_stock' => 1,
- ]);
- $attachedSku = ProductSKU::factory()->create([
- 'order_id' => $order->id,
- 'product_id' => $attachedProduct->id,
- 'maf_order_id' => $mafOrder->id,
- 'status' => 'отгружен',
- ]);
- ProductSKU::factory()->create([
- 'order_id' => $order->id,
- 'product_id' => $otherProduct->id,
- 'maf_order_id' => null,
- 'status' => 'требуется',
- ]);
- $response = $this->actingAs($this->adminUser)
- ->post(route('order.store'), [
- 'id' => $order->id,
- 'products' => [$otherProduct->id],
- 'quantity' => [1],
- ]);
- $response->assertRedirect();
- $response->assertSessionHas('danger', function ($messages) {
- return str_contains(implode(' ', (array) $messages), 'Нельзя удалить привязанные МАФ');
- });
- $this->assertDatabaseHas('products_sku', [
- 'id' => $attachedSku->id,
- 'maf_order_id' => $mafOrder->id,
- 'deleted_at' => null,
- ]);
- }
- public function test_store_order_cannot_add_new_positions_when_has_attached_maf(): void
- {
- $attachedProduct = Product::factory()->create();
- $existingProduct = Product::factory()->create();
- $newProduct = Product::factory()->create();
- $order = Order::factory()->create([
- 'order_status_id' => Order::STATUS_NEW,
- ]);
- $mafOrder = MafOrder::factory()->create([
- 'product_id' => $attachedProduct->id,
- 'in_stock' => 1,
- ]);
- ProductSKU::factory()->create([
- 'order_id' => $order->id,
- 'product_id' => $attachedProduct->id,
- 'maf_order_id' => $mafOrder->id,
- 'status' => 'отгружен',
- ]);
- ProductSKU::factory()->create([
- 'order_id' => $order->id,
- 'product_id' => $existingProduct->id,
- 'maf_order_id' => null,
- 'status' => 'требуется',
- ]);
- $response = $this->actingAs($this->adminUser)
- ->post(route('order.store'), [
- 'id' => $order->id,
- 'products' => [$attachedProduct->id, $existingProduct->id, $newProduct->id],
- 'quantity' => [1, 1, 1],
- ]);
- $response->assertRedirect();
- $response->assertSessionHas('danger', function ($messages) {
- return str_contains(implode(' ', (array) $messages), 'Нельзя добавлять новые позиции МАФ');
- });
- $this->assertDatabaseMissing('products_sku', [
- 'order_id' => $order->id,
- 'product_id' => $newProduct->id,
- 'deleted_at' => null,
- ]);
- }
- public function test_move_maf_transfers_sku_to_another_order(): void
- {
- $product = Product::factory()->create();
- $order1 = Order::factory()->create();
- $order2 = Order::factory()->create();
- $productSku = ProductSKU::factory()->create([
- 'order_id' => $order1->id,
- 'product_id' => $product->id,
- ]);
- // This route requires admin role
- $response = $this->actingAs($this->adminUser)
- ->post(route('order.move-maf'), [
- 'new_order_id' => $order2->id,
- 'ids' => json_encode([$productSku->id]),
- ]);
- $response->assertStatus(200);
- $response->assertJson(['success' => true]);
- $productSku->refresh();
- $this->assertEquals($order2->id, $productSku->order_id);
- }
- // ==================== Photo Management ====================
- public function test_can_upload_photo_to_order(): void
- {
- Storage::fake('public');
- $order = Order::factory()->create();
- // Use create() instead of image() to avoid GD extension requirement
- $photo = UploadedFile::fake()->create('photo.jpg', 100, 'image/jpeg');
- $response = $this->actingAs($this->managerUser)
- ->post(route('order.upload-photo', $order), [
- 'photo' => [$photo],
- ]);
- $response->assertRedirect(route('order.show', $order));
- $this->assertCount(1, $order->fresh()->photos);
- }
- public function test_can_delete_photo_from_order(): void
- {
- Storage::fake('public');
- $order = Order::factory()->create();
- $file = File::factory()->create();
- $order->photos()->attach($file);
- // This route requires admin role
- $response = $this->actingAs($this->adminUser)
- ->delete(route('order.delete-photo', [$order, $file]));
- $response->assertRedirect(route('order.show', $order));
- $this->assertCount(0, $order->fresh()->photos);
- }
- public function test_can_delete_all_photos_from_order(): void
- {
- Storage::fake('public');
- $order = Order::factory()->create();
- $files = File::factory()->count(3)->create();
- $order->photos()->attach($files->pluck('id'));
- // This route requires admin role
- $response = $this->actingAs($this->adminUser)
- ->delete(route('order.delete-all-photos', $order));
- $response->assertRedirect(route('order.show', $order));
- $this->assertCount(0, $order->fresh()->photos);
- }
- // ==================== Document Management ====================
- public function test_can_upload_document_to_order(): void
- {
- Storage::fake('public');
- $order = Order::factory()->create();
- $document = UploadedFile::fake()->create('document.pdf', 100);
- $response = $this->actingAs($this->managerUser)
- ->post(route('order.upload-document', $order), [
- 'document' => [$document],
- ]);
- $response->assertRedirect(route('order.show', $order));
- $this->assertCount(1, $order->fresh()->documents);
- }
- public function test_upload_document_preserves_unicode_and_quotes_filename(): void
- {
- Storage::fake('public');
- $order = Order::factory()->create();
- $filename = "Документ «смета» 'финал' \"v2\".pdf";
- $document = UploadedFile::fake()->create($filename, 100, 'application/pdf');
- $response = $this->actingAs($this->managerUser)
- ->post(route('order.upload-document', $order), [
- 'document' => [$document],
- ]);
- $response->assertRedirect(route('order.show', $order));
- $saved = $order->fresh()->documents->first();
- $this->assertNotNull($saved);
- $this->assertEquals($filename, $saved->original_name);
- $this->assertEquals('orders/' . $order->id . '/document/' . $filename, $saved->path);
- Storage::disk('public')->assertExists($saved->path);
- }
- public function test_upload_statement_preserves_unicode_and_quotes_filename(): void
- {
- Storage::fake('public');
- $order = Order::factory()->create();
- $filename = "Акт «приемки» 'этап 1' \"финал\".pdf";
- $statement = UploadedFile::fake()->create($filename, 100, 'application/pdf');
- $response = $this->actingAs($this->managerUser)
- ->post(route('order.upload-statement', $order), [
- 'statement' => [$statement],
- ]);
- $response->assertRedirect(route('order.show', $order));
- $saved = $order->fresh()->statements->first();
- $this->assertNotNull($saved);
- $this->assertEquals($filename, $saved->original_name);
- $this->assertEquals('orders/' . $order->id . '/statement/' . $filename, $saved->path);
- Storage::disk('public')->assertExists($saved->path);
- }
- public function test_upload_document_limits_to_5_files(): void
- {
- Storage::fake('public');
- $order = Order::factory()->create();
- $documents = [];
- for ($i = 0; $i < 7; $i++) {
- $documents[] = UploadedFile::fake()->create("document{$i}.pdf", 100);
- }
- $this->actingAs($this->managerUser)
- ->post(route('order.upload-document', $order), [
- 'document' => $documents,
- ]);
- $this->assertCount(5, $order->fresh()->documents);
- }
- public function test_can_upload_webp_photo(): void
- {
- Storage::fake('public');
- $order = Order::factory()->create();
- $photo = UploadedFile::fake()->create('photo.webp', 100, 'image/webp');
- $response = $this->actingAs($this->managerUser)
- ->post(route('order.upload-photo', $order), [
- 'photo' => [$photo],
- ]);
- $response->assertRedirect();
- $saved = $order->fresh()->photos->first();
- $this->assertNotNull($saved);
- $this->assertSame('photo.webp', $saved->original_name);
- }
- // ==================== Generation ====================
- public function test_generate_installation_pack_is_allowed_for_any_status_when_data_is_valid(): void
- {
- Bus::fake();
- $product = Product::factory()->create();
- $mafOrder = MafOrder::factory()->create(['product_id' => $product->id]);
- $order = Order::factory()->create([
- 'order_status_id' => Order::STATUS_NEW,
- ]);
- ProductSKU::factory()->create([
- 'order_id' => $order->id,
- 'product_id' => $product->id,
- 'maf_order_id' => $mafOrder->id,
- ]);
- $response = $this->actingAs($this->managerUser)
- ->get(route('order.generate-installation-pack', $order));
- $response->assertRedirect(route('order.show', $order));
- $response->assertSessionHas('success');
- Bus::assertDispatched(GenerateInstallationPack::class);
- }
- public function test_generate_installation_pack_still_requires_connected_maf(): void
- {
- Bus::fake();
- $product = Product::factory()->create();
- $order = Order::factory()->create([
- 'order_status_id' => Order::STATUS_IN_MOUNT,
- ]);
- ProductSKU::factory()->create([
- 'order_id' => $order->id,
- 'product_id' => $product->id,
- 'maf_order_id' => null,
- ]);
- $response = $this->actingAs($this->managerUser)
- ->get(route('order.generate-installation-pack', $order));
- $response->assertRedirect(route('order.show', $order));
- $response->assertSessionHas('danger');
- Bus::assertNotDispatched(GenerateInstallationPack::class);
- }
- public function test_admin_can_create_ttn_with_departure_date_and_increment_counter(): void
- {
- Bus::fake();
- Setting::set(Setting::KEY_TTN_NEXT_NUMBER, 10);
- $product = Product::factory()->create();
- $order = Order::factory()->create([
- 'installation_date' => '2026-04-15',
- ]);
- $sku = ProductSKU::factory()->create([
- 'order_id' => $order->id,
- 'product_id' => $product->id,
- ]);
- $response = $this->actingAs($this->adminUser)
- ->post(route('order.create-ttn'), [
- 'order_number' => 'З-100',
- 'order_date' => '2026-04-07',
- 'departure_date' => '2026-04-08',
- 'order_sum' => '150000',
- 'skus' => [$sku->id],
- ]);
- $response->assertRedirect();
- $response->assertSessionHas('success');
- $this->assertDatabaseHas('ttns', [
- 'ttn_number' => 10,
- 'order_number' => 'З-100',
- 'order_date' => '2026-04-07',
- 'departure_date' => '2026-04-08',
- 'order_sum' => '150000',
- ]);
- $this->assertSame(11, Setting::getInt(Setting::KEY_TTN_NEXT_NUMBER));
- $ttn = Ttn::query()->where('ttn_number', 10)->firstOrFail();
- $this->assertSame([(string) $sku->id], array_map('strval', json_decode($ttn->skus, true)));
- Bus::assertDispatched(GenerateTtnPack::class);
- }
- // ==================== Export ====================
- public function test_can_export_orders(): void
- {
- Order::factory()->count(3)->create();
- // This route requires admin role
- $response = $this->actingAs($this->adminUser)
- ->post(route('order.export'));
- $response->assertRedirect(route('order.index'));
- $response->assertSessionHas('success');
- }
- public function test_can_export_single_order(): void
- {
- $order = Order::factory()->create();
- // This route requires admin role
- $response = $this->actingAs($this->adminUser)
- ->post(route('order.export-one', $order));
- $response->assertRedirect(route('order.show', $order));
- $response->assertSessionHas('success');
- }
- }
|