# Переделка миграции проектов — План 4: админка (тумблер + очистка) + ЛК

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Доделать UI эпика: глобальный тумблер режима экспорта (online/batch) в админке; ручной экран «Проекты у поставщика» (кто заказывал · дата последней поставки · bulk-удаление); в ЛК — обязательный выбор региона + явная опция «Вся РФ» с предупреждением и подтверждением.

**Architecture:** Бэкенд — `AdminSupplierIntegrationController` +4 метода (getExportMode/setExportMode/projectsIndex/projectsDestroy), удаление на портале через `SupplierPortalClient::deleteProject` (pivot CASCADE снимает локальные связи). Фронт — тумблер в `AdminSupplierIntegrationView.vue` + новый `AdminSupplierProjectsView.vue` (таблица + bulk-delete) + роут/nav. ЛК — `NewProjectDialog.vue`: вернуть sentinel «Вся РФ» (code 0), взаимоисключение с субъектами, предупреждение-диалог + блок submit без выбора. «Require region» — UI-гейт (на бэке `regions=[]` = «Вся РФ» неотличим от «забыл»; backend-валидация `present|array` без изменений).

**Tech Stack:** PHP 8.3 / Laravel 13 / Vue 3 + Vuetify 3 / Pest 4 / Vitest.

**Spec:** [docs/superpowers/specs/2026-05-20-project-migration-redesign-design.md](../specs/2026-05-20-project-migration-redesign-design.md) §4.1 (тумблер), §4.7 (очистка), §4.8 (ЛК).

**Prereq:** Планы 1+2+3 исполнены (pivot, subject_code, supplier_export_mode seed, SupplierExportMode резолвер, per-субъект supplier_projects).

---

## Task 1: тумблер режима экспорта (бэкенд + UI)

**Files:**

- Modify: `app/app/Http/Controllers/Api/AdminSupplierIntegrationController.php` (+`getExportMode`, +`setExportMode`)
- Modify: `app/routes/*` (роуты в admin-группе рядом с supplier-integration; найти по `AdminSupplierIntegrationController`)
- Modify: `app/resources/js/views/admin/AdminSupplierIntegrationView.vue` (+тумблер)
- Test: `app/tests/Feature/Admin/SupplierExportModeEndpointTest.php`

- [ ] **Step 1: Написать падающий тест (бэкенд)**

```php
<?php

declare(strict_types=1);

use App\Models\SaasAdminUser;
use Illuminate\Support\Facades\DB;

it('GET export-mode returns current; POST switches it', function (): void {
    $admin = SaasAdminUser::factory()->create(); // адаптировать под фактическую admin-аутентификацию проекта
    DB::table('system_settings')->where('key', 'supplier_export_mode')->update(['value' => 'batch']);

    $this->actingAs($admin, 'saas_admin')
        ->getJson('/api/admin/supplier-integration/export-mode')
        ->assertOk()->assertJson(['mode' => 'batch']);

    $this->actingAs($admin, 'saas_admin')
        ->postJson('/api/admin/supplier-integration/export-mode', ['mode' => 'online'])
        ->assertOk()->assertJson(['mode' => 'online']);

    expect(DB::table('system_settings')->where('key', 'supplier_export_mode')->value('value'))->toBe('online');
});

it('POST export-mode rejects invalid value', function (): void {
    $admin = SaasAdminUser::factory()->create();
    $this->actingAs($admin, 'saas_admin')
        ->postJson('/api/admin/supplier-integration/export-mode', ['mode' => 'turbo'])
        ->assertStatus(422);
});
```

(Admin-guard/factory — привести к фактическому паттерну из существующих `app/tests/Feature/Admin/*` тестов.)

- [ ] **Step 2: Прогнать — падает**

Run: `php artisan test --filter=SupplierExportModeEndpointTest`
Expected: FAIL — методов/роутов нет.

- [ ] **Step 3: Добавить методы в контроллер**

В `AdminSupplierIntegrationController`:

```php
    public function getExportMode(): JsonResponse
    {
        return response()->json(['mode' => \App\Services\Supplier\SupplierExportMode::current()]);
    }

    public function setExportMode(Request $request): JsonResponse
    {
        $data = $request->validate([
            'mode' => ['required', 'in:online,batch'],
        ]);

        DB::table('system_settings')->updateOrInsert(
            ['key' => 'supplier_export_mode'],
            ['value' => $data['mode'], 'type' => 'string', 'updated_at' => now()],
        );

        return response()->json(['mode' => $data['mode']]);
    }
```

- [ ] **Step 4: Зарегистрировать роуты**

Рядом с существующими роутами `AdminSupplierIntegrationController` (найти `supplier-integration` в `app/routes/`), в той же группе (auth:saas_admin / admin middleware):

```php
Route::get('admin/supplier-integration/export-mode', [AdminSupplierIntegrationController::class, 'getExportMode']);
Route::post('admin/supplier-integration/export-mode', [AdminSupplierIntegrationController::class, 'setExportMode']);
```

- [ ] **Step 5: Прогнать тест — зелёный**

Run: `php artisan test --filter=SupplierExportModeEndpointTest`
Expected: PASS (2). Полный вывод.

- [ ] **Step 6: UI-тумблер в `AdminSupplierIntegrationView.vue`**

Добавить секцию «Режим экспорта проектов» с `<v-switch>` / `<v-btn-toggle>` (online|batch): на mount GET `/api/admin/supplier-integration/export-mode`, при смене POST. Подпись: «Онлайн — перенос сразу при правке; Пакетный — ночной 18:00». Vitest-стаб API.

- [ ] **Step 7: Vitest для тумблера**

`app/resources/js/views/admin/__tests__/AdminSupplierIntegrationView.export-mode.spec.ts` — mount, mock GET возвращает batch → переключение шлёт POST online. Run: `npm run test:vue -- AdminSupplierIntegrationView.export-mode`. Expected: PASS.

- [ ] **Step 8: pint + larastan + Коммит**

```bash
composer pint; composer stan
git add -- app/app/Http/Controllers/Api/AdminSupplierIntegrationController.php app/routes app/resources/js/views/admin/AdminSupplierIntegrationView.vue app/resources/js/views/admin/__tests__/AdminSupplierIntegrationView.export-mode.spec.ts app/tests/Feature/Admin/SupplierExportModeEndpointTest.php
git commit -m "feat(admin): supplier export-mode toggle (online|batch) endpoint + UI" -- app/app/Http/Controllers/Api/AdminSupplierIntegrationController.php app/routes app/resources/js/views/admin/AdminSupplierIntegrationView.vue app/resources/js/views/admin/__tests__/AdminSupplierIntegrationView.export-mode.spec.ts app/tests/Feature/Admin/SupplierExportModeEndpointTest.php
```

---

## Task 2: экран «Проекты у поставщика» — бэкенд (список + bulk-delete)

**Files:**

- Modify: `app/app/Http/Controllers/Api/AdminSupplierIntegrationController.php` (+`projectsIndex`, +`projectsDestroy`)
- Modify: `app/routes/*` (2 роута)
- Test: `app/tests/Feature/Admin/SupplierProjectsAdminTest.php`

- [ ] **Step 1: Написать падающий тест**

```php
<?php

declare(strict_types=1);

use App\Models\Project;
use App\Models\SaasAdminUser;
use App\Models\SupplierProject;
use App\Models\Tenant;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;

it('lists supplier projects with orderers and last delivery date', function (): void {
    $admin = SaasAdminUser::factory()->create();
    $tenant = Tenant::factory()->create(['name' => 'ООО Ромашка']);
    $sp = SupplierProject::query()->create([
        'platform' => 'B1', 'signal_type' => 'site', 'unique_key' => 'okna.ru',
        'subject_code' => 82, 'current_limit' => 5, 'sync_status' => 'ok', 'supplier_external_id' => '777',
    ]);
    $project = Project::factory()->create(['tenant_id' => $tenant->id, 'is_active' => true]);
    DB::table('project_supplier_links')->insert([
        'project_id' => $project->id, 'supplier_project_id' => $sp->id, 'platform' => 'B1', 'subject_code' => 82,
    ]);

    $resp = $this->actingAs($admin, 'saas_admin')
        ->getJson('/api/admin/supplier-integration/projects')->assertOk()->json();

    $row = collect($resp['projects'])->firstWhere('id', $sp->id);
    expect($row['unique_key'])->toBe('okna.ru')
        ->and($row['subject_code'])->toBe(82)
        ->and($row['orderers'])->toContain('ООО Ромашка');
});

it('bulk-deletes selected supplier projects on portal + locally (pivot cascades)', function (): void {
    Http::fake(['*/admin/visit/rt-project-delete' => Http::response(['status' => 'OK'], 200)]);
    $admin = SaasAdminUser::factory()->create();
    $sp = SupplierProject::query()->create([
        'platform' => 'B1', 'signal_type' => 'site', 'unique_key' => 'del.ru',
        'subject_code' => 82, 'current_limit' => 0, 'sync_status' => 'ok', 'supplier_external_id' => '888',
    ]);

    $this->actingAs($admin, 'saas_admin')
        ->postJson('/api/admin/supplier-integration/projects/delete', ['ids' => [$sp->id]])
        ->assertOk()->assertJson(['deleted' => 1]);

    expect(SupplierProject::find($sp->id))->toBeNull();
    Http::assertSent(fn ($r) => str_contains($r->url(), 'rt-project-delete'));
});
```

- [ ] **Step 2: Прогнать — падает**

Run: `php artisan test --filter=SupplierProjectsAdminTest`
Expected: FAIL — методов/роутов нет.

- [ ] **Step 3: Добавить методы**

В `AdminSupplierIntegrationController` (+ импорт `use App\Services\Supplier\SupplierPortalClient;`):

```php
    public function projectsIndex(): JsonResponse
    {
        $conn = DB::connection('pgsql_supplier');

        $projects = $conn->table('supplier_projects as sp')
            ->select('sp.id', 'sp.platform', 'sp.signal_type', 'sp.unique_key', 'sp.subject_code', 'sp.supplier_external_id', 'sp.current_limit', 'sp.inactive_since')
            ->orderBy('sp.unique_key')->orderBy('sp.subject_code')->orderBy('sp.platform')
            ->get()
            ->map(function ($sp) use ($conn): array {
                $orderers = $conn->table('project_supplier_links as psl')
                    ->join('projects as p', 'p.id', '=', 'psl.project_id')
                    ->join('tenants as t', 't.id', '=', 'p.tenant_id')
                    ->where('psl.supplier_project_id', $sp->id)
                    ->where('p.is_active', true)
                    ->distinct()->pluck('t.name')->all();

                $lastDelivery = $conn->table('supplier_leads')
                    ->where('supplier_project_id', $sp->id)
                    ->max('created_at');

                return [
                    'id' => $sp->id,
                    'platform' => $sp->platform,
                    'signal_type' => $sp->signal_type,
                    'unique_key' => $sp->unique_key,
                    'subject_code' => $sp->subject_code !== null ? (int) $sp->subject_code : null,
                    'subject_name' => $sp->subject_code !== null
                        ? (\App\Support\RussianRegions::CODE_TO_NAME[(int) $sp->subject_code] ?? null)
                        : 'РФ',
                    'current_limit' => (int) $sp->current_limit,
                    'orderers' => $orderers,
                    'last_delivery_at' => $lastDelivery,
                ];
            });

        return response()->json(['projects' => $projects->all()]);
    }

    public function projectsDestroy(Request $request, SupplierPortalClient $client): JsonResponse
    {
        $data = $request->validate([
            'ids' => ['required', 'array', 'min:1'],
            'ids.*' => ['integer'],
        ]);

        $deleted = 0;
        $failures = [];

        foreach (SupplierProject::whereIn('id', $data['ids'])->get() as $sp) {
            try {
                if ($sp->supplier_external_id !== null) {
                    $client->deleteProject((int) $sp->supplier_external_id);
                }
                $sp->delete(); // project_supplier_links снимутся CASCADE
                $deleted++;
            } catch (\Throwable $e) {
                $failures[] = ['id' => $sp->id, 'error' => $e->getMessage()];
            }
        }

        return response()->json(['deleted' => $deleted, 'failures' => $failures]);
    }
```

- [ ] **Step 4: Роуты**

```php
Route::get('admin/supplier-integration/projects', [AdminSupplierIntegrationController::class, 'projectsIndex']);
Route::post('admin/supplier-integration/projects/delete', [AdminSupplierIntegrationController::class, 'projectsDestroy']);
```

- [ ] **Step 5: Прогнать тест — зелёный**

Run: `php artisan test --filter=SupplierProjectsAdminTest`
Expected: PASS (2). Полный вывод; failed — file:line.

- [ ] **Step 6: pint + larastan + Коммит**

```bash
composer pint; composer stan
git add -- app/app/Http/Controllers/Api/AdminSupplierIntegrationController.php app/routes app/tests/Feature/Admin/SupplierProjectsAdminTest.php
git commit -m "feat(admin): supplier projects list (orderers, last delivery) + bulk delete" -- app/app/Http/Controllers/Api/AdminSupplierIntegrationController.php app/routes app/tests/Feature/Admin/SupplierProjectsAdminTest.php
```

---

## Task 3: экран «Проекты у поставщика» — фронтенд

**Files:**

- Create: `app/resources/js/views/admin/AdminSupplierProjectsView.vue`
- Modify: `app/resources/js/router/index.ts` (роут `/admin/supplier-projects`)
- Modify: `app/resources/js/layouts/AdminLayout.vue` (nav-пункт)
- Test: `app/resources/js/views/admin/__tests__/AdminSupplierProjectsView.spec.ts`

- [ ] **Step 1: Написать падающий Vitest**

```ts
import { mount } from '@vue/test-utils';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createVuetify } from 'vuetify';
import AdminSupplierProjectsView from '../AdminSupplierProjectsView.vue';
import { apiClient } from '../../../api/client';

vi.mock('../../../api/client', () => ({ apiClient: { get: vi.fn(), post: vi.fn() }, ensureCsrfCookie: vi.fn() }));

describe('AdminSupplierProjectsView', () => {
    beforeEach(() => vi.clearAllMocks());

    it('renders rows with orderers + last delivery and bulk-deletes selected', async () => {
        (apiClient.get as any).mockResolvedValue({ data: { projects: [
            { id: 1, platform: 'B1', unique_key: 'okna.ru', subject_name: 'Москва', current_limit: 5, orderers: ['ООО Ромашка'], last_delivery_at: '2026-05-19T10:00:00Z' },
        ] } });
        (apiClient.post as any).mockResolvedValue({ data: { deleted: 1, failures: [] } });

        const wrapper = mount(AdminSupplierProjectsView, { global: { plugins: [createVuetify()] } });
        await flushPromises();
        expect(wrapper.text()).toContain('okna.ru');
        expect(wrapper.text()).toContain('ООО Ромашка');

        // выбрать строку + удалить
        await wrapper.find('[data-testid="row-checkbox-1"]').setValue(true);
        await wrapper.find('[data-testid="bulk-delete-btn"]').trigger('click');
        // подтверждение → confirm
        await wrapper.find('[data-testid="confirm-delete-btn"]').trigger('click');
        await flushPromises();
        expect(apiClient.post).toHaveBeenCalledWith('/api/admin/supplier-integration/projects/delete', { ids: [1] });
    });
});
```

(`flushPromises` — импортировать из `@vue/test-utils`; адаптировать селекторы под фактическую разметку компонента.)

- [ ] **Step 2: Прогнать — падает**

Run: `npm run test:vue -- AdminSupplierProjectsView`
Expected: FAIL — компонента нет.

- [ ] **Step 3: Создать `AdminSupplierProjectsView.vue`**

Таблица `<v-data-table>` колонки: чекбокс · Источник (`unique_key`) · Платформа · Регион (`subject_name`) · Лимит · Кто заказывал (`orderers.join(', ')`) · Последняя поставка (`last_delivery_at` форматом даты, «—» если null). Над таблицей — `<v-btn data-testid="bulk-delete-btn" :disabled="selected.length===0">Удалить выбранные</v-btn>`. По клику — `<v-dialog>` подтверждения с `data-testid="confirm-delete-btn"` → POST `/api/admin/supplier-integration/projects/delete` `{ids: selected}` → refresh + snackbar (deleted/failures). Иконки — Lucide через IconSet (не mdi). Загрузка списка на mount GET `/api/admin/supplier-integration/projects`.

- [ ] **Step 4: Роут + nav**

- `router/index.ts`: `{ path: '/admin/supplier-projects', component: () => import('../views/admin/AdminSupplierProjectsView.vue'), meta: { layout: 'app', requiresAdmin: true } }` (адаптировать под фактический meta-паттерн admin-роутов).
- `AdminLayout.vue`: nav-пункт «Проекты у поставщика» (Lucide-иконка).

- [ ] **Step 5: Прогнать Vitest — зелёный**

Run: `npm run test:vue -- AdminSupplierProjectsView`
Expected: PASS. Полный вывод.

- [ ] **Step 6: type-check + lint + Коммит**

```bash
npm run type-check; npm run lint:vue
git add -- app/resources/js/views/admin/AdminSupplierProjectsView.vue app/resources/js/router/index.ts app/resources/js/layouts/AdminLayout.vue app/resources/js/views/admin/__tests__/AdminSupplierProjectsView.spec.ts
git commit -m "feat(admin): supplier projects cleanup screen (list + bulk delete)" -- app/resources/js/views/admin/AdminSupplierProjectsView.vue app/resources/js/router/index.ts app/resources/js/layouts/AdminLayout.vue app/resources/js/views/admin/__tests__/AdminSupplierProjectsView.spec.ts
```

---

## Task 4: ЛК — обязательный регион + «Вся РФ» с предупреждением

**Files:**

- Modify: `app/resources/js/views/projects/NewProjectDialog.vue`
- Test: `app/resources/js/views/projects/__tests__/NewProjectDialog.regions.spec.ts`

- [ ] **Step 1: Написать падающий Vitest**

```ts
import { mount } from '@vue/test-utils';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createVuetify } from 'vuetify';
import NewProjectDialog from '../NewProjectDialog.vue';

vi.mock('../../../api/client', () => ({ apiClient: { post: vi.fn().mockResolvedValue({}) }, ensureCsrfCookie: vi.fn(), extractErrorMessage: () => '' }));

const mountDialog = () => mount(NewProjectDialog, {
    props: { modelValue: true, mode: 'create' },
    global: { plugins: [createVuetify()], stubs: { DevIndexBadge: true } },
});

describe('NewProjectDialog regions gate', () => {
    beforeEach(() => vi.clearAllMocks());

    it('blocks submit when no region chosen', async () => {
        const w = mountDialog();
        // заполнить остальные обязательные поля минимально...
        await w.find('[data-testid="submit-btn"]').trigger('click');
        const { apiClient } = await import('../../../api/client');
        expect(apiClient.post).not.toHaveBeenCalled();
        expect(w.text()).toContain('Выберите регион'); // ошибка-валидации
    });

    it('selecting «Вся РФ» shows warning and requires confirm before submit; sends regions=[]', async () => {
        const w = mountDialog();
        // выбрать «Вся РФ» (sentinel)
        await w.vm.$nextTick();
        (w.vm as any).chooseVsyaRf?.(); // или установить form.regions=[0] через UI
        await w.vm.$nextTick();
        expect(w.text()).toContain('всю Россию'); // предупреждение
        await w.find('[data-testid="confirm-vsya-rf"]').trigger('click');
        // заполнить прочие поля + submit
        await w.find('[data-testid="submit-btn"]').trigger('click');
        const { apiClient } = await import('../../../api/client');
        const payload = (apiClient.post as any).mock.calls[0][1];
        expect(payload.regions).toEqual([]);
    });
});
```

(Селекторы/хелперы — адаптировать под фактическую разметку; ключевые проверки: (1) пустой выбор → submit заблокирован + ошибка; (2) «Вся РФ» → предупреждение + подтверждение → `regions:[]`.)

- [ ] **Step 2: Прогнать — падает**

Run: `npm run test:vue -- NewProjectDialog.regions`
Expected: FAIL — гейта/опции «Вся РФ»/предупреждения нет.

- [ ] **Step 3: Доработать `NewProjectDialog.vue`**

- Вернуть «Вся РФ» в опции: `selectableRegions` ([NewProjectDialog.vue:154](../../../app/resources/js/views/projects/NewProjectDialog.vue#L154)) — включить sentinel `code:0` (или отдельный чекбокс «Вся РФ»).
- **Взаимоисключение:** выбор «Вся РФ» очищает конкретные субъекты и наоборот.
- **Предупреждение:** при выборе «Вся РФ» — `<v-dialog>`/`<v-alert>` «Вы выбрали всю Россию — проект будет получать лиды по всем регионам» + кнопка `data-testid="confirm-vsya-rf"`; без подтверждения «Вся РФ» не считается выбранной.
- **Гейт submit:** `submit()` блокируется (ошибка `errors.regions = ['Выберите регион']`), если не выбрано ни субъектов, ни подтверждённой «Вся РФ». Submit-кнопка `:disabled` либо ранний `return` в `submit()`.
- **Маппинг на API:** «Вся РФ» → `form.regions = []` (sentinel 0 не отправлять); конкретные → коды субъектов как сейчас.
- Подпись поля: «Источник» / регион — оставить «Регионы» (но убрать «(пусто = вся РФ)», т.к. пусто теперь = не выбрано / ошибка).

- [ ] **Step 4: Прогнать Vitest — зелёный**

Run: `npm run test:vue -- NewProjectDialog.regions`
Expected: PASS. Полный вывод. Прогнать также существующие `NewProjectDialog` specs — не сломаны (если дефолт изменился — поправить старые ожидания «regions=[]»).

- [ ] **Step 5: type-check + lint + Коммит**

```bash
npm run type-check; npm run lint:vue
git add -- app/resources/js/views/projects/NewProjectDialog.vue app/resources/js/views/projects/__tests__/NewProjectDialog.regions.spec.ts
git commit -m "feat(projects): require region + explicit «Вся РФ» with warning gate" -- app/resources/js/views/projects/NewProjectDialog.vue app/resources/js/views/projects/__tests__/NewProjectDialog.regions.spec.ts
```

---

## Task 5: Регрессия эпика (полная)

**Files:** нет (проверка).

- [ ] **Step 1: Backend full Supplier/Admin/Jobs**

Run: `php artisan test app/tests/Feature/Supplier app/tests/Feature/Admin app/tests/Feature/Jobs app/tests/Feature/Schedule app/tests/Unit/Services`
Expected: GREEN. passed/failed; failed — file:line.

- [ ] **Step 2: Frontend Vitest**

Run: `npm run test:vue`
Expected: GREEN (или `--maxWorkers=2` при OOM — квирк 98).

- [ ] **Step 3: Полный regression sweep**

Run: `/regression full` (Pest --parallel + Larastan + Vitest + Vite build + lychee + gitleaks). Привести каноническую статус-строку + вердикт.

- [ ] **Step 4 (verification-before-completion):** перед «План 4 / эпик готов» — invoke `superpowers:verification-before-completion`; фактический вывод, не «должно проходить».

---

## Self-review (выполнен при написании плана)

- **Покрытие spec §4.1/§4.7/§4.8:** тумблер режима endpoint+UI (T1); экран очистки — список (кто заказывал через pivot→projects→tenants, дата последней поставки = max supplier_leads.created_at) + bulk-delete через `deleteProject` + CASCADE pivot (T2 бэкенд, T3 фронт); ЛК require-region UI-гейт + «Вся РФ» предупреждение/подтверждение/взаимоисключение, regions=[] на API (T4).
- **Без плейсхолдеров:** бэкенд-код полный (контроллер, тесты). Фронт — структура компонента + ключевая логика + Vitest с `data-testid`; точные селекторы помечены «адаптировать под разметку» (компонент создаётся в этом же таске — не внешняя неизвестность).
- **Согласованность типов:** `mode` ∈ {online,batch} единообразно (резолвер План 3 + endpoint); `projectsIndex` отдаёт `subject_code:int|null` + `subject_name` (RussianRegions из Плана 2); `projectsDestroy` `{ids:int[]}` → `{deleted,failures}`; ЛК → `regions:int[]` (пусто=Вся РФ) совпадает с StoreProjectRequest `present|array`.
- **Граница require-region:** на бэке `regions=[]` (Вся РФ) и «забыл» неотличимы → гейт намеренно UI-only (T4); backend-валидация `present|array` не меняется (P1: Вся РФ — валидный пустой массив).
- **Риск:** admin-аутентификация в тестах (`saas_admin` guard / factory) — привести к фактическому паттерну существующих `app/tests/Feature/Admin/*`; роуты — в ту же группу, что текущие `AdminSupplierIntegrationController` методы.
