# Runbook: импорт проектов lkomega → info@lkomega.ru

Разовая операция на боевом liderra.ru (`111.88.246.137`). Усыновляет активные
проекты поставщика crm.bp-gr.ru (аккаунт lkomega) как проекты Лидерры под
тенантом info@lkomega.ru. **Портал не трогается** (никаких save/update/delete).

Plan: `docs/superpowers/plans/2026-05-22-supplier-projects-import-lkomega.md`
Spec: `docs/superpowers/specs/2026-05-22-supplier-projects-import-lkomega-design.md`

## Деплой команды

Скопировать на сервер в `/var/www/liderra/app` (бэкап заменяемого `SupplierRegions.php`):

- `app/Support/SupplierRegions.php` (изменён — добавлен `mapFromSupplier`)
- `app/Services/Supplier/Import/SupplierImportMapper.php` (новый)
- `app/Services/Supplier/Import/SupplierProjectImporter.php` (новый)
- `app/Console/Commands/ImportSupplierProjectsCommand.php` (новый)

```bash
cp /var/www/liderra/app/app/Support/SupplierRegions.php /var/www/liderra/app/app/Support/SupplierRegions.php.bak-$(date +%Y%m%d-%H%M%S)
# scp 4 файла...
cd /var/www/liderra/app && php artisan optimize:clear   # сброс кэша команд/конфига
```

Команда — не очередь/воркер, `queue:restart` не нужен.

## Шаг 1 — dry-run (показать план, ничего не пишет)

```bash
cd /var/www/liderra/app && php artisan supplier:import-projects --tenant=info@lkomega.ru
```

Вывод: число проектов к созданию, таблица (тип / идентификатор[маскирован] /
тег / регионы / лимит / площадки B1:id …), список пропусков (`unsupported_source`
для dop2, `regions_exclude`, `sms_unparseable`, `already_exists`).
**Показать заказчику → получить «ок».**

## Шаг 2 — реальный прогон

```bash
cd /var/www/liderra/app && php artisan supplier:import-projects --tenant=info@lkomega.ru --commit
```

Вывод: `Создано: проектов=N, supplier_projects=M, связок=K.`

## Шаг 3 — пост-проверка

```bash
# Число проектов под тенантом (подставить tenant_id info@lkomega.ru):
php artisan tinker --execute="echo App\Models\Project::on('pgsql_supplier')->where('tenant_id', <ID>)->count();"
```

- Выборочно сверить 2–3 проекта: `daily_limit_target` = сумме площадок; регионы корректны (ГИБДД→Лидерра).
- **Проверить целостность площадок каждого проекта** (см. оговорку ниже):
  каждый проект должен иметь столько связок `project_supplier_links`, сколько площадок было в группе (обычно 3).

  ```bash
  php artisan tinker --execute="App\Models\Project::on('pgsql_supplier')->where('tenant_id',<ID>)->get()->each(fn(\$p)=>print(\$p->id.': '.\$p->supplierProjects()->count().PHP_EOL));"
  ```

- Подтвердить, что на портале crm.bp-gr.ru **НЕ появилось новых проектов** (команда его не дёргает).

## Атомарность

`commit()` оборачивает запись **каждого проекта в отдельную транзакцию** на проде
(`DB::connection('pgsql_supplier')->transaction(...)` — Project + все `supplier_projects` +
все pivot-связки группы атомарно). Сбой посреди группы → транзакция откатывается → ни
проекта, ни partial-связок не остаётся, БД консистентна. Уже созданные ДО сбоя проекты
сохраняются (per-group, не per-run).

В прод-команде это включается автоматически: гейт `getPdo()->inTransaction()` — false на
проде → BEGIN/COMMIT per item; true только под тестовым харнессом `SharesSupplierPdo`
(общий PDO уже в транзакции) → внутренний BEGIN пропускается, чтобы избежать
«already active transaction» в Pest.

При ошибке посреди прогона — просто запустить `--commit` повторно: идемпотентность
(`already_exists` по tenant+signal + `firstOrCreate` по `(platform, unique_key,
subject_code)`) пропустит уже импортированные проекты и до-создаст оставшиеся.

## Откат

Импортированные проекты под тенантом — soft-archive через ЛК или:

```php
App\Models\Project::on('pgsql_supplier')->where('tenant_id', <ID>)
    ->update(['is_active' => false, 'archived_at' => now()]);
```

`supplier_projects`/pivot можно оставить (они указывают на реальные портальные проекты,
их используют и другие потоки).

## NB про среду

- На worktree-сборке 2 теста `SupplierPortalClient*Test` падают из-за отсутствия node-модуля
  `playwright` — это известный worktree-only квирк (не регрессия), на боевом/основном
  checkout с `node_modules` они зелёные.
- Larastan: production-код чист; test-only `TestCall`/Mockery (квирк #25) добавляются в
  `phpstan-baseline.neon` на чистом checkout при интеграции (не из worktree — там дрейф
  ide-helper искажает счётчики).
