docs(autopodbor): план — вписаны находки Задачи 0 (pgsql_supplier, web.php, FieldWorkspaceScreen)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-07-08 17:10:36 +03:00
parent b2da19c8d0
commit 0035dd4fae
2 changed files with 16 additions and 23 deletions
@@ -70,13 +70,13 @@ ls app/resources/js/components | grep -iE "konkurent|pole|autopodbor|competit"
```
Expected: получить точные имена `AutopodborChargeService::chargeForRun` (сигнатура), `Tenant::requiredLeadsForTomorrow()`, `PricingTierRepository::activeAt()`, файл маршрутов автоподбора, имя Vue-компонента «Конкурентное поле» и его подвала выбора. **Записать найденное в шапку плана-исполнения** — на них ссылаются Задачи 2,4,5,7,8.
- [ ] **Step 4: Зафиксировать, как ставится tenant-контекст в системных (межтенантных) запросах.** Распорядителю нужен запрос «есть ли `running` study по ВСЕМ тенантам». Свериться, как это делают существующие джобы/команды (поиск роли/обхода RLS):
- [ ] **Step 4: Зафиксировать, как ставится tenant-контекст в системных (межтенантных) запросах.** Распорядителю нужен запрос «есть ли `running` study по ВСЕМ тенантам».
Run:
```bash
grep -rn "set_config('app.current_tenant\|BYPASSRLS\|crm_supplier_worker\|withoutTenantScope\|SET app.current_tenant" app/ | head -20
```
Expected: понять паттерн системного доступа (напр. отдельное соединение/роль в джобах). Заложить его в Задачу 3. Если единого паттерна нет — использовать выделенный запрос от системной роли; согласовать с `rls-reviewer`.
**РЕЗУЛЬТАТ РАЗВЕДКИ (2026-07-08, заполнено):**
- Джобы автоподбора ходят под `crm_app_user` (FORCE RLS) и ставят `set_config('app.current_tenant_id', ...)` per-tenant — **межтенантного пути у них нет**.
- Штатный системный путь в проекте: роль **`crm_supplier_worker` (BYPASSRLS)** через соединение **`pgsql_supplier`** (trait `SharesSupplierPdo`, прецедент — `RouteSupplierLeadJob`). Это и есть «системный» доступ для queue-воркеров через тенанты.
- **Решение:** распорядитель (`AutopodborStudyScheduler::systemQuery()`) ходит через `DB::connection('pgsql_supplier')`. RLS-политики НЕ трогаем. Тесты распорядителя — `uses(SharesSupplierPdo::class)` (см. `feedback-prod-full-test-isolated-db`). Согласовать с `rls-reviewer` (Задача 3).
- Точное имя соединения подтвердить в `config/database.php` (`pgsql_supplier`).
---
@@ -508,24 +508,17 @@ final class AutopodborStudyScheduler
/**
* Системный (межтенантный) билдер БД — распорядителю нужно видеть ВСЕ тенанты.
* ПОДСТАВИТЬ реальный путь из Задачи 0 Step 4 (отдельное соединение/роль crm_supplier_worker
* или системный запрос без tenant-контекста). Ниже — плейсхолдер-обёртка для читаемости.
* Соединение `pgsql_supplier` = роль crm_supplier_worker (BYPASSRLS), штатный системный
* путь queue-воркеров (прецедент RouteSupplierLeadJob / trait SharesSupplierPdo). Задача 0 Step 4.
*/
private function systemQuery(): \Illuminate\Database\Query\Builder
{
// TODO(Task 0 Step 4): вернуть builder на системном соединении с обходом RLS.
return DB::connection($this->systemConnectionName())->table('autopodbor_runs');
}
private function systemConnectionName(): string
{
// Имя соединения из config/database.php, под которым джобы автоподбора уже ходят межтенантно.
return config('autopodbor.system_connection', config('database.default'));
return DB::connection('pgsql_supplier')->table('autopodbor_runs');
}
}
```
> **Важно исполнителю:** `systemQuery()`/`systemConnectionName()` — единственное место с неопределённостью. В Задаче 0 Step 4 найден реальный способ межтенантного доступа (как в существующих джобах). Подставить его сюда ДО прогона межтенантных тестов; согласовать с `rls-reviewer`. Функционально `pickNextRunId()` уже полон.
> **RLS-нить:** `systemQuery()` идёт через `pgsql_supplier` (BYPASSRLS) — это и даёт межтенантную видимость. Политики НЕ ослабляем. Прогнать `rls-reviewer` на этот сервис. Тесты — `uses(SharesSupplierPdo::class)`. `pickNextRunId()` функционально полон.
- [ ] **Step 4: Запустить тест — PASS.**
@@ -908,11 +901,11 @@ public function studyBatch(Request $request, AutopodborRunService $svc): JsonRes
return response()->json(['batch_id' => $res->batch_id, 'queued' => $res->queued], 202);
}
```
Маршрут (рядом с существующим `study`):
Маршрут — в **`app/routes/web.php`**, внутри существующей группы `Route::middleware(['auth:sanctum,impersonation','tenant'])->prefix('/api/autopodbor')`, рядом с `POST /study` (строка ~359):
```php
Route::post('autopodbor/study/batch', [AutopodborController::class, 'studyBatch']);
Route::post('/study/batch', 'App\Http\Controllers\Api\AutopodborController@studyBatch');
```
> Способ получить tenant/`$uid` скопировать 1:1 из существующего `study()` (Задача 0 Step 3) — не изобретать.
> Способ получить tenant/`$uid` скопировать 1:1 из существующего `study()` в `AutopodborController` — не изобретать (там уже разрешён tenant из middleware `tenant`).
- [ ] **Step 4: Запустить тесты — PASS.**
@@ -929,8 +922,8 @@ git commit -m "feat(autopodbor): endpoint POST /api/autopodbor/study/batch"
## Task 8: Фронт — кнопка в подвале выбора + яркий алерт баланса + статусы
**Files:** (точные имена — из Задачи 0 Step 3)
- Modify: Vue-компонент «Конкурентное поле» (список + нижний подвал выбора), api-модуль автоподбора.
**Files:** (уточнено разведкой Задачи 0)
- Modify: `app/resources/js/views/autopodbor/screens/FieldWorkspaceScreen.vue` (список + нижний подвал выбора «Выбрано конкурентов: N»), `app/resources/js/api/autopodbor.ts`, при необходимости `app/resources/js/stores/autopodborStore.ts`. Исполнителю: открыть `FieldWorkspaceScreen.vue` и подтвердить, что подвал выбора именно там (иначе — соседний `FieldCompetitorScreen.vue`).
- Test: `app/tests/Frontend/KonkurentnoePoleBatch.spec.ts`
- [ ] **Step 1: Добавить API-вызов в фронтовый api-модуль.**