Files
portal/docs/superpowers/plans/2026-06-20-acceptance-PR2-projects-plan.md
T
Дмитрий 7be1c93e12 docs(приёмка): поправки планов по находкам ревью (Татарстан/капча/lpimp/воронка/№23/append-only)
Закрыты 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>
2026-06-21 05:38:37 +03:00

15 KiB
Raw Blame History

Приёмка 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/projectsProjectController::store :125-165.
  • Гейт реквизитов — первый проект без реквизитов → requisites_required 422 — :131-133. Значит перед PR2 реквизиты у каждого клиента должны быть заполнены (минимум subject_type+contact_name+contact_phone, +inn для ЮЛ/ИП — RequisitesService.php:31-44).
  • Префлайт баланса при создании — если суммарный дневной лимит проектов не обеспечен балансом → balance_insufficient 409 — :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); для sitesignal_identifier домен; для call7XXXXXXXXXX; для smssms_senders[] (+опц. sms_keyword) — StoreProjectRequest.php:17-47.
  • После создания online-режим шлёт SyncSupplierProjectJob → upsert supplier_projects + pivot project_supplier_linksSyncSupplierProjectJob.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, заводить проекты ДО подъёма или с балансом ≥ требуемого.