Портал публикует /api/sales/integration/{managers,prospects} под сервис-токеном
(X-Sales-Token, config sales.integration_token). ingest создаёт карточки stage=new
с полным payload, дедуп по (sales_user_id, inn|phone), assigned_by=начальник.
Гейты: 8/8 Pest, Larastan 0. Финдер-сторона — следующим коммитом.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 правки в §15: карточка показывает ВСЕ данные поиска (payload), фильтр —
конкретный выбор менеджера, сортировка «Переговоры» по ближайшему созвону.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Этап 1 Task 14. /sales/prospects (менеджер), /sales/prospects/board
(начальник, salesBossOnly). Пункты «Потенциальные клиенты» и «Воронка отдела».
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Этап 1 Task 12–13. SalesProspectsView (менеджер, listProspects свои),
SalesProspectsBoardView (начальник, listSalesManagers фильтр, все карточки).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Этап 1 Task 11. Без drag: карточки раскладываются по стадиям, клик эмитит
open, просроченный next_call_at подсвечивается.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Этап 1 Task 10. Селектор результата, поля по стадии, отказ скрыт при user,
обязательные причина/время. Тесты через vm (v-dialog телепортит контент).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Этап 1 Task 9. PROSPECT_STAGES (8 колонок), stageMeta, isOverdue;
listProspects/updateProspect в api/sales.ts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Этап 1 Task 8. По 2 карточки на каждую из 8 стадий у указанного менеджера
(для показа досок). Baseline под artisan() Pest-паттерн.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Этап 1 Task 3–7. GET /api/sales/prospects (менеджер видит свои; начальник —
все + ?manager_id). PATCH /prospects/{id} — переговоры (next_call_at),
недозвон (причина), отказ (причина; запрещён из stage=user). ownership 403.
8 тестов зелёные. Baseline Larastan под Pest-паттерны нового файла.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Этап 1 Task 2. Дефолт stage=new в $attributes (доступен до refresh).
ide-helper мисин добавлен в локальный стаб (регенерация всех моделей).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
15 задач по TDD: миграция sales_prospects, модель, GET/PATCH API (свои/все+
фильтр, результаты переговоры/недозвон/отказ с правилами), демо-команда, доска-
канбан + диалог карточки + экраны менеджера и начальника + меню/маршруты.
Этапы 2 (поиск→портал) и 3 (автожизнь) — отдельными планами.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Начальник отдаёт фирмы из поиска менеджеру → канбан в кабинете портала.
7 колонок (Новые/Переговоры/Зарегистрировался/Пополнил баланс/Пользователь/
Отказ/Недозвон), движение карточки — по результату разговора (без drag),
автостадии по реальным пополнениям, интеграция поиск→портал по сервис-токену.
Общая доска начальнику + фильтр по менеджерам. 3 этапа. Согласовано в чате.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Форма «Тарифы менеджеров» дефолтит первую ступень с 0 (с момента привязки),
и расчёт вознаграждения (tenure стартует с 1) с from=0 работает верно, но
валидатор params.periods.*.from требовал min:1 → «Сохранить» падало 422
(«Поле params.periods.0.from должно быть не менее 1»). min:1 → min:0.
TDD: тест store с первой ступенью from=0 → 201 (падал 422 до фикса).
Baseline Larastan: actingAs 20→21 (добавлен один тест, квирк Pest+Larastan п.25).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Перегенерированный baseline дословно цитирует тексты ошибок PHPStan, включая
строковые литералы из тест-файлов (синтетические телефоны Казани 8432… из
tests/Unit/Autopodbor/Extract/HtmlPhoneScannerTest.php). Сами тесты уже в
allowlist; baseline — эхо их кода, своих ПДн не содержит.
Та же категория, что composer.lock и cspell-words.txt.
Найдено при догоняющей проверке gitleaks после merge-коммита.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Прод приведён к gitea-main 94043ea6 один-в-один (rsync-checksum, потерь нет);
бэкап цел; экран Тенанты на серверную пагинацию выкачен; 992 фронт-теста зелёные;
деньги t2=1 839 405₽ целы. Остатки не-git на проде объяснены (.bak-precutover до 03.07).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Свод за заход «закрывай хвосты»: разбор и фикс preflight-sweep/reminder (no-op с 26.06
из-за RLS-роли очереди), self-heal 4 проектов на проде, деньги t2 целы, playwright в deps.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Корень: после переезда на Managed PG очередь ходит под ролью crm_app_user (RLS),
и Tenant::query() в BalancePreflightSweepJob/BalanceFrozenReminderJob отдавал 0 строк
без app.current_tenant_id — биллинг-преflight молча стал no-op с 26.06 (ни заморозок,
ни снятия проектных блоков). Перечень тенантов теперь берётся через pgsql_supplier
(BYPASSRLS), модель грузится внутри per-tenant SET LOCAL контекста. Логика проверена
на боевых данных: t25/t26 снимутся, t27/t30 заморозятся.
Playwright рантайма supplier-портала объявлен в dependencies ровно 1.59.0 под
chromium-1217 + package-lock синхронизирован; деплой ставит его npm ci --omit=dev,
durable к чистке node_modules.
Тесты Billing 18/18, pint/phpstan чисто.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Фундамент под сквозную вложенность: periodRange() читает date_from/date_to
(приоритет) либо preset; Финансы и Клиенты считаются по выбранному периоду через
whereBetween. FE: «Свой период» + два date-поля + «Применить» → date_from/date_to.
Спека дизайна A+B+C+масштаб сохранена. Baseline перегенерирован (getJson тестов).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Дашборд не было видно в сайдбаре — уйдя с него, нельзя было быстро вернуться.
Добавлен первый nav-пункт «Командный центр» → /admin/dashboard (иконка dashboard).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6-я плитка «👥 Клиенты» со светофором (amber если есть спящие) + drill:
KPI за период (всего активных / новых / заходили / получали лиды / платили),
список новых клиентов (с датой входа/лидами/балансом) и «спящих» (активные
без входа 14+ дней или ни разу = не активировались). Клик по строке → карточка
клиента. Backend: clients() endpoint + clientsTile в summary (cross-tenant через
pgsql_admin); сигналы — users.last_login_at, deals, balance_transactions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Переиспользование одного DB-билдера в цикле накапливало where-клаузы →
updateOrInsert уходил в INSERT существующей строки → SQLSTATE 23505 на проде
при повторном сборе. Билдер теперь создаётся внутри цикла. + тест на 2 прогона.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- DadataBalanceProvider: эндпоинт profile/balance требует X-Secret вместе с Token
(был HTTP 401 на проде при первом сборе); добавлен заголовок при наличии secret.
- BalanceHealth: отрицательный баланс больше не даёт «−1 дн.» (кламп max(0, days)).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Требование владельца 28.06: прямая ссылка оплаты у каждого сервиса в дашборде
(на планшете). topup_url — статика из конфига; YC строится из billing_account_id.
3 read-only эндпоинта под группой [saas-admin,admin-db] (cross-tenant через
pgsql_admin): L1 сводка (Финансы+Здоровье), L2 Финансы (KPI+внимание+топ),
L2 Здоровье (6 подсистем+светофор). TDD, 83 admin-теста зелёные. baseline:
+3 Pest getJson false-positive. Без маржи, без новых таблиц.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
10 задач по TDD: 3 backend-эндпоинта (summary/finance/health) под admin-db
группой, Vue-экран AdminDashboardView + роут /admin/dashboard, тесты, выкат.
Без новых таблиц; переиспуют существующие detail-экраны как Уровень 3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Старый per-instance экспорт больше не используется (заменён глобальным
installMenuRepositionFix). Старый тест-файл удалён - механизм покрыт
installMenuRepositionFix.spec.ts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Корень дефекта живого клиента 27.06: список Тип лица в окне создания
проекта уезжал за экран, реквизиты не сохранялись 422. Обход вешался
вручную на каждый список и забыт в 3 окнах. Решение - включать обход
автоматически глобально через MutationObserver, убрать ручные пометки.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AdminTenantsController/AdminBillingController ходят под default-подключением;
новое pgsql_admin (crm_admin_user, srv_bypass) даст им cross-tenant доступ
через middleware-переключатель (следующий коммит). На dev fallback на
DB_USERNAME. Test: pgsql_admin делит базовый pgsql-конфиг, роль из DB_ADMIN_*.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Поправка по факту кода: реально сломаны только AdminTenantsController и
AdminBillingController (ходят под default crm_app_user); Incidents/Pd/
SupplierIntegration/Impersonation уже используют pgsql_supplier и работают.
План: connection pgsql_admin + middleware UseAdminConnection (admin-db).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Корень: после переезда на Managed PG админка ходит под crm_app_user без
cross-tenant доступа; штатная роль crm_admin_user готова, но не подключена.
Способ A: pgsql_admin connection + middleware-переключатель на админ-группе.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Повод: 26.06.2026 параллельная сессия выполнила yc managed-postgresql database
delete liderra + recreate на боевом кластере → переналила схему со старыми
небезопасными RLS-политиками → вход в портал лёг (см. db/CHANGELOG_schema.md v8.57).
- .claude/hooks/prod-db-guard.mjs (PreToolUse Bash|PowerShell): блокирует ТОЛЬКО
снос/пересоздание боевой базы/кластера (yc database/cluster delete, DROP DATABASE
liderra). Обычную работу (чтение, запросы, тесты на liderra_testing, migrate)
НЕ трогает. Override владельца: маркер PROD-DESTROY-OK или env ALLOW_PROD_DB_DESTROY=1.
Проверено 7 сценариями + живым запуском (echo с паттерном заблокирован).
- .claude/hooks/prod-db-pointer.mjs (SessionStart): инжектит указатель «живая база =
кластер c9q2cvtjpq3hgq6l0r96, старая копия на VM не трогать, тесты на liderra_testing»
— чтобы сессия не путала актуальную БД со stale-копией и не «пересобирала».
- .claude/settings.json: deny-паттерны (yc database/cluster delete) + оба хука.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Магазин ЮKassa (1392092) с включённой фискализацией требует секцию receipt на
каждом платеже. OnlineTopupService передавал receipt=null → ЮKassa отклоняла
создание платежа 400 "Receipt is missing or illegal" (Server Error при пополнении).
- OnlineTopupService::start теперь формирует receipt: customer.email (почта
пользователя, fallback на mail.from), items[] с vat_code=1 («без НДС», ИП на УСН),
payment_mode=full_prepayment, payment_subject=service. Передаём всегда (магазин
требует чек безусловно). Формат проверен живым запросом к боевому API → HTTP 200.
- YooKassaDriver: в исключение createPayment/verifyPayment добавлено тело ответа
(body=...), чтобы причина 4xx была видна в логе сразу.
- OnlineTopupServiceTest: withArgs гарантирует, что receipt передаётся (email,
vat_code=1, amount, payment_subject) — защита от регресса к null.
Проверено: Pest passed, Pint clean, формат чека → HTTP 200 на api.yookassa.ru.
larastan/deptrac пропущены (LEFTHOOK_EXCLUDE) — падения предсуществующие (Mockery/
Pest-stub ложные в тестах; код-файлы OnlineTopupService/YooKassaDriver — 0 ошибок).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
REVOKE на tenant_subscriptions (нет в продукте) и ALTER OWNER на webhook_log
(удалена в v8.35 legacy-webhook removal) вызывали ошибки при провижене ролей.
Убраны. Проверено: повторный прогон 02_grants.sql на полигоне — без ошибок.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Балансовый блок проектов закрыт 6 находками (N/G/E/H/M/O), по TDD+визуал,
gitea/main 8eef54ac..696e75b9, на прод НЕ катилось. E подтверждён живым
заказом в кабинете поставщика + уборка. L (омега tenant 25/проект 188)
остаётся открытым — сверка после деплоя + GO. Также зафиксирован долг
larastan(env-косяк ide-helper)/deptrac(ProjectResource->SupplierSnapshotGuard).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Боковая панель ProjectDetailsDrawer ловила в onSave только 422; при 409
balance_insufficient (лимит превышает баланс) ничего не показывала — клиент
не понимал, почему правка лимита не сохранилась. Теперь под полем лимита
выводится причина с ёмкостью и запрошенным объёмом + призыв пополнить баланс.
TDD: ProjectDetailsDrawer.spec — 409 balance_insufficient показывает сообщение,
saved не эмитится. Глаза: лимит 100 при ёмкости 60 → PATCH 409 → видимое
«Лимит превышает баланс: хватает на 60 лид(ов), запрошено 105…».
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Диалог «Редактировать» делал Object.assign(form, project) и слал весь объект,
включая sms_senders=null для site/call. UpdateProjectRequest валидирует
sms_senders как sometimes|array|min:1 → present-null проваливал array → 422.
Поле sms на форме site/call не отрисовано, поэтому errors.sms_senders некуда
показать — диалог «висел молча». Теперь persist() для не-sms проектов не шлёт
sms_senders/sms_keyword (заодно не триггерит зря snapshot-guard).
TDD: NewProjectDialog.spec — правка site не содержит sms-полей в PATCH body.
Глаза: правка site-проекта → PATCH 200, имя изменилось, диалог закрылся.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Заблокированный за нехваткой баланса проект выглядел как обычный «Ожидает
синхр.» — клиент не понимал, почему лиды не идут. Теперь карточка показывает
приоритетный красный статус «Приостановлен — не хватает баланса».
- ProjectResource: новое read-only поле balance_blocked (preflight_blocked_at !== null)
- ProjectCard: статус-бейдж приоритетно показывает блок над sync_status
- Project type: balance_blocked?: boolean
TDD: backend 2/2 (ProjectResource), frontend ProjectCard 6/6. ProjectResource
регрессия (applies_from/source_lock) 6/6 GREEN.
larastan/deptrac исключены точечно — пред-существующая краснота.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>