Закрыты doc-находки двух ревью-сессий (точность планов перед прод-прогоном): - GAP-1: Татарстан 16→19 (16=Мордовия, конституц. порядок ст.65, НЕ ГИБДД) — свод, план №3 (C-2), PR2 (сноска: P5 [16]=Мордовия, не Татарстан). Сверено по RussianRegions.php. - M-2/GAP-3: капча = NullCaptchaVerifier БЕЗУСЛОВНО (не только local) — план №18. - lpimp-status: lpimp_ → 401 на биллинг/api-keys (не 403), 403 только на admin, /api/billing/charges читается — план №25. - N-6: воронка статусов НЕ форсится (любой валидный slug, нет state-machine) — план №13. - №23-tx: импорт пишет 1 нулевую historical_import строку; сверять баланс/lead_charges, не число balance_transactions — план №23. - append-only: lead_charges защищён GRANT'ом (prod/crm_app_user), не триггером → на dev-superuser правка пройдёт (ложный GREEN); гонять на проде — план №12. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
15 KiB
Приёмка liderra.ru — ПУНКТ PR2: завести 16 проектов через портал → 12 rt у поставщика
Для исполнителя: план в формате PR1 (одобрен владельцем 20.06). Каждый шаг =
🔧 Код-факт (file:line)·🎬 Действие·📋 ОТЧЁТ (было→ожидали→стало + 📸/📝)·❌ Если не так. Программистские детали остаются «под капотом»; владельцу в эфир идут только человеческие карточки.
Цель: показать, что клиент заводит свои проекты через портал (а не «из-под капота»), и портал сам передаёт их поставщику в правильном, сгруппированном виде — 16 проектов в кабинетах превращаются в 12 «живых» (rt) у поставщика, лишнее не задваивается.
Что снимаем глазами: раздел «Проекты» в кабинете (16 карточек по 7 клиентам) — живые 📸. Экран администратора поставщика (12 rt) — 🟦 только на боевом прогоне (локально нет реального поставщика crm.bp-gr.ru).
Источник сценария: R1 §«Проекты (16)» (docs/superpowers/runbooks/2026-06-18-acceptance-r1-provisioning.md), карточка PR2.
🔧 Код-факты (подтверждено чтением 20.06)
- Создание проекта: POST /api/projects → ProjectController::store :125-165.
- Гейт реквизитов — первый проект без реквизитов →
requisites_required422 — :131-133. Значит перед PR2 реквизиты у каждого клиента должны быть заполнены (минимумsubject_type+contact_name+contact_phone, +innдля ЮЛ/ИП — RequisitesService.php:31-44). - Префлайт баланса при создании — если суммарный дневной лимит проектов не обеспечен балансом →
balance_insufficient409 — :140-156. (16 проектов × лимит 10 = до 60 лидов/день на C1; при 100 000 ₽ и 500 ₽/лид запас 200 — проходит.) - Поля запроса:
name, signal_type(site|call|sms), daily_limit_target(1..10000), regions[](present, каждый 1..89, []=вся РФ), delivery_days_mask(1..127); дляsite—signal_identifierдомен; дляcall—7XXXXXXXXXX; дляsms—sms_senders[](+опц.sms_keyword) — StoreProjectRequest.php:17-47. - После создания online-режим шлёт SyncSupplierProjectJob → upsert
supplier_projects+ pivotproject_supplier_links— SyncSupplierProjectJob.php:29-56. - Группировка (почему 16→12): rt у поставщика = по
(платформа × уникальный ключ источника)— SupplierProjectGrouping::resolvePlatforms :51-64:- сайт/звонок → B1+B2+B3 (3 rt на один ключ),
- смс+слово → B2+B3 (2 rt),
- смс без слова → B3 (1 rt),
- DIRECT → 0 rt (поставке не передаётся).
Итог R1: корень-сайт 3 · субдомен 3 · звонок 3 · смс+слово 2 · смс 1 · DIRECT 0 = 12. Несколько клиентов на одном источнике (P1..P7 на
test-okna.ru) схлопываются в те же 3 rt — это и есть «лишнее не задваивается».
- Локальный засев клиентов/проектов: ImitationSeedCommand :72-96 (factory, баланс 100 000 ₽, обход гейта). Засев вставляет pivot напрямую и НЕ ходит к реальному поставщику — поэтому «12 rt у поставщика» локально не показать.
Популяция проектов (R1 §«Проекты (16)»)
| P# | Клиент | Источник | Тип | Регионы | Лимит | Дни |
|---|---|---|---|---|---|---|
| P1 | C1 | test-okna.ru | сайт | [82] | 10 | 127 |
| P2 | C2 | test-okna.ru | сайт | [82] | 10 | 127 |
| P3 | C3 | test-okna.ru | сайт | [83] | 10 | 127 |
| P4 | C4 | test-okna.ru | сайт | [] вся РФ | 10 | 127 |
| P5 | C5 | test-okna.ru | сайт | [16] | 10 | 127 |
| P6 | C6 | test-okna.ru | сайт | [82,83] | 10 | 127 |
| P7 | C7 | test-okna.ru | сайт | [56] | 10 | 127 |
| P8 | C1 | msk.test-okna.ru | сайт | [82] | 10 | 127 |
| P9 | C1 | 79990001122 | звонок | [82] | 10 | 127 |
| P10 | C2 | 79990001122 | звонок | [82] | 10 | 127 |
| P11 | C1 | TESTKW +слово «окна» | смс+слово | [82] | 10 | 127 |
| P12 | C2 | TESTKW +слово «окна» | смс+слово | [82] | 10 | 127 |
| P13 | C1 | TESTSMS | смс | [82] | 10 | 127 |
| P14 | C2 | TESTSMS | смс | [82] | 10 | 127 |
| P15 | C1 | test-direct.ru | DIRECT | [82] | 10 | 127 |
| P16 | C2 | test-direct.ru | DIRECT | [82] | 10 | 127 |
Коды регионов — порядковые 1..89 (конституционный порядок, НЕ ГИБДД): [82]=Москва, [83]=СПб, [56]=Московская обл, [16]=Мордовия (P5 — это Мордовия, не Татарстан!), [19]=Татарстан. Сверено по
RussianRegions.php.
rt у поставщика (итог): 12 (корень-сайт 3 · субдомен 3 · звонок 3 · смс+слово 2 · смс 1 · DIRECT 0).
Шаг PR2-0 — Предусловие: реквизиты заполнены (иначе гейт)
🔧 Код: ProjectController::store :131-133. 🎬 Действие (лок): у каждого из 7 клиентов заполнить минимальные реквизиты (вкладка «Реквизиты»). На засеянных factory-клиентах гейт уже обойдён — шаг нужен, если клиенты заведены онбордингом.
📋 ОТЧЁТ:
| Что | Было | Ожидалось | Стало (факт) | Чем подтверждаем |
|---|---|---|---|---|
| Первый проект без реквизитов | — | портал не даёт создать: «нужны реквизиты» | (слот прогона) | 📸 экран Проекты с подсказкой про реквизиты |
| После заполнения реквизитов | гейт стоит | гейт снят, проект можно создать | (слот) | 📸 вкладка Реквизиты «Готово» |
❌ Если не так: проект создаётся без реквизитов → дефект гейта (пункт #19 ловит это отдельно).
Шаг PR2-1 — Завести 16 проектов через портал
🔧 Код: ProjectController::store :125-165; поля — StoreProjectRequest :17-47.
🎬 Действие (лок): под каждым клиентом создать его проект(ы) по таблице P1–P16 — кнопкой «Новый проект» в разделе «Проекты» (или API POST /api/projects с полями из код-фактов). 16 раз.
📋 ОТЧЁТ:
| Что | Было | Ожидалось | Стало (факт) | Чем подтверждаем |
|---|---|---|---|---|
| Проектов у 7 клиентов | 0 (или только засеянный) | 16 проектов по таблице, тип/регионы/лимит верны | (слот) | 📸 раздел «Проекты» каждого клиента (карточки) |
| Проектов в базе | — | 16 строк projects под тест-клиентами |
(слот) | 📝 SQL SELECT count(*) FROM projects WHERE tenant_id IN (<TEST>) = 16 |
💡 Что внутри: портал записал 16 проектов, по одному на строку таблицы; у каждого свой источник, регионы и дневной лимит. Это «карта», по которой портал будет принимать заявки.
❌ Если не так: проект не создаётся (422 реквизиты / 409 баланс) → вернуться к PR2-0 / поднять баланс (PR3); тип/регионы не те → ошибка ввода.
Шаг PR2-2 — Проверить, что проекты видны и корректны в кабинете
🔧 Код: ProjectController::index :39-122 (фильтры/сортировка/пагинация — раздел «Проекты»). 🎬 Действие (лок): открыть раздел «Проекты» под C1 (6 проектов) и под C2 (5 проектов) — глазами сверить источник/регионы/лимит с таблицей.
📋 ОТЧЁТ:
| Что | Было | Ожидалось | Стало (факт) | Чем подтверждаем |
|---|---|---|---|---|
| Карточки проектов C1 | пусто | 6 карточек (P1,P8,P9,P11,P13,P15), параметры верны | (слот) | 📸 «Проекты» под C1 |
| Карточки проектов C2 | пусто | 5 карточек (P2,P10,P12,P14,P16) | (слот) | 📸 «Проекты» под C2 |
| Чужие проекты | — | клиент видит только свои (изоляция) | (слот) | 📸 (свой список без чужих) |
❌ Если не так: проект чужого клиента виден → утечка (пункт #16 ловит отдельно); параметры не совпали → ошибка создания.
Шаг PR2-3 — Синхронизация к поставщику: 12 rt + pivot (🟦 на прод-прогоне)
🔧 Код: SyncSupplierProjectJob :29-56; группировка SupplierProjectGrouping::resolvePlatforms :51-64. 🎬 Действие (прод): после создания проектов через портал — открыть админку поставщика и сверить число rt-проектов; в базе проверить pivot. 🎬 Действие (лок): показать нельзя (нет реального crm.bp-gr.ru) — вместо экрана даём 💡-объяснение группировки по коду.
📋 ОТЧЁТ:
| Что | Было | Ожидалось | Стало (факт) | Чем подтверждаем |
|---|---|---|---|---|
| rt у поставщика | — | 12 rt (3+3+3+2+1+0 по группам) | (слот боевого прогона) | 📸 админка поставщика (🟦 прод) |
pivot project_supplier_links |
пусто | заполнен для B-каналов | (слот) | 📝 SQL SELECT count(*) FROM project_supplier_links WHERE … (внутр.) |
| Схлопывание дублей | — | 7 клиентов на test-okna.ru → те же 3 rt, не 21 |
(слот) | 💡 объяснение по коду группировки |
💡 Что внутри: хоть проектов 16, поставщику не нужно 16 отдельных «приёмников». Портал умно объединяет: все, кто ловит с одного источника, садятся на один и тот же приёмник у поставщика. Поэтому 16 кабинетных проектов = 12 реальных приёмников; ничего не задвоено, ничего лишнего поставщику не ушло. (DIRECT-проекты поставщику вообще не передаются — заявки по ним приходят другим путём.)
❌ Если не так: rt не появились → синк упал (логи) или режим не online; pivot пуст → инъекция B-канала не сматчится (ломает R2/R3).
Verification PR2 (выход из пункта)
- 📸 Раздел «Проекты» под C1 (6) и C2 (5) — карточки с верными источником/регионами/лимитом.
- 📸 (или сводно) у остальных C3–C7 их проекты видны.
- 📝 SQL:
count(projects WHERE tenant_id IN TEST)= 16. - 🟦 на прод-прогоне: админка поставщика = 12 rt; pivot заполнен.
- 📸 изоляция: клиент видит только свои проекты.
📸 vs 📝 в этом пункте
- 📸 Скриншот: раздел «Проекты» каждого клиента (16 карточек суммарно), вкладка «Реквизиты», (прод) админка поставщика.
- 📝 Текст: число строк
projects, заполнение pivot, лог синка — глазами в портале не видны.
Грабли
- На local синк к реальному поставщику не идёт → «12 rt» только на прод-прогоне; локально доказываем 16 проектов + механику группировки.
- DIRECT (
test-direct.ru, P15/P16) — у поставщика 0 rt (по дизайну); заявки по нему вливаются DIRECT-каналом, не B-каналом. - Префлайт баланса 409 при больших лимитах — для PR2 лимиты 10, запас есть; если поднимали лимиты под R4, заводить проекты ДО подъёма или с балансом ≥ требуемого.