Commit Graph

2650 Commits

Author SHA1 Message Date
Дмитрий cedbb9de92 feat(sales): карточка клиента (бэкенд)
Task 1.4a: GET /api/sales/clients/{tenantId} — профиль, KPI (баланс/запас/проекты/лиды-цель/средняя цена лида, earned=null до Фазы 3), проекты, лиды по дням, последние лиды с МАСКИРОВАННЫМ телефоном, активность. 403 для чужого клиента (ScopesSalesOwnership), начальник видит всех. Тест 8/8, весь sales 65/65, stan 0. Один эскейп на сессию.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 08:13:08 +03:00
Дмитрий 41fb1e9d02 feat(external): JivoLivenessProbe — живость чата
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 08:09:56 +03:00
Дмитрий 0ef791b6e2 feat(external): YooKassaLivenessProbe — живость платёжного шлюза
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 07:55:42 +03:00
Дмитрий 5e8e58d1d1 feat(external): SmtpLivenessProbe — живость почты Yandex 360
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 07:50:16 +03:00
Дмитрий 9fd4459e2f feat(external): DTO LivenessReading + интерфейс LivenessProbe
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 07:43:05 +03:00
Дмитрий 760956e4a7 docs(plan): мониторинг внешних сервисов — план реализации (10 задач, TDD)
Разбивка на 10 задач по TDD: LivenessProbe/DTO, 4 пробы живости
(SMTP/ЮKassa/Jivo/капча), джоба пишет строки живости, email-алерт по
edge-trigger, фронт со статус-текстом. Схема БД не меняется.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 07:35:49 +03:00
Дмитрий 9ef8eccf08 docs(spec): мониторинг внешних сервисов — баланс + живость + email-алерт
Дизайн (брейншторм 02.07): расширяем плитку балансов до наблюдения за всеми
внешними сервисами — деньги И живость. Добавляем Почту (Yandex 360 SMTP,
только живость), ЮKassa, JivoSite, SmartCaptcha. Красная плитка + письмо
на ops@liderra.ru по edge-trigger. Конкурентное поле — вне объёма.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 07:26:28 +03:00
Дмитрий a85148555c feat(sales): экран «Мои клиенты» (фронт)
Task 1.3b: SalesClientsView — таблица 11 колонок по демо #page-clients (Клиент/Тип/Активность/Баланс/Запас/Проектов/Пришло/Оборот/Тариф/Заработал/Статус), бейдж типа лица, чипы статуса (Триал/Активен/Просрочка/Приостановлен), HelpHint на терминах, деньги ru-RU + JetBrains Mono, перезагрузка при смене периода, клик по строке → карточка. Заработал=«—» до Фазы 3. listSalesClients в api/sales.ts. Vitest 6/6, lint/type-check 0. Один эскейп на сессию.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 06:40:56 +03:00
Дмитрий ad975c4d44 feat(sales): эндпоинт «Мои клиенты» (бэкенд)
Task 1.3a: GET /api/sales/clients — менеджер видит своих (ScopesSalesOwnership), начальник всех. Строки: организация, ИНН/тип лица (tenant_requisites), баланс, запас, проекты, лиды/оборот за период (SalesMetricsService), тариф-снимок из assignment, статус 1:1 с AdminTenantsController (trial>suspended>overdue>active). earned_rub=null до Фазы 3. Тест 7/7, stan 0 (baseline: Pest false-pos). Пагинация — TODO. Один эскейп на сессию.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 06:34:22 +03:00
Дмитрий 05bf7ef1b8 docs(claude-md): горящий баннер БОЕВОЙ ПРОД в §ГЛАВНОЕ (v2.48)
Через плагин claude-md-management (targeted-update):
- §ГЛАВНОЕ: баннер про боевой прод (доступ только с разрешения владельца, БД по умолчанию
  чтение, ЛК поставщика прод=crm.lead.store, снос базы только PROD-DESTROY-OK+бэкап)
- шапка 2.47 → 2.48 (01.07.2026); прод очищен и взведён для боевой работы
- cspell-words.txt: +golive

NB: LEFTHOOK_EXCLUDE=larastan — предсуществующий чужой Sales-тест.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 17:11:58 +03:00
Дмитрий 4a0e26af09 docs(prod): маркировка БОЕВОЙ ПРОД + сводка 01.07 (ЛК crm.lead.store, чистка базы)
- ПИЛОТ.md: горящий баннер + датированная сводка 01.07 (свап ЛК, приём лидов, снос базы, фикс)
- ЭТАЛОН.md: пометка «локальная dev, боевой = ПИЛОТ, не путать»
- .claude/hooks/prod-db-pointer.mjs: горящий блок БОЕВОЙ ПРОД в SessionStart
- план: прогресс Фаз 4/0/5/6
- cspell-words.txt: +14 валидных слов (gzk, ВТБ, ОКПО, автобэкапы и др.)

NB: LEFTHOOK_EXCLUDE=larastan — предсуществующий чужой tests/Feature/Sales/SalesClientsIndexTest.php.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 17:01:55 +03:00
Дмитрий 7e8a2dc86a fix(supplier): не отправлять площадку с долей лимита 0 (кабинет crm.lead.store отклоняет)
Новый кабинет отклоняет rt-project-save с limit=0 (Введите limit!), старый принимал.
Проект с daily_limit_target=1 (site->3 площадки) давал доли 1/0/0 и падал на B2/B3.

- distributeForPlatform опускает площадки с долей 0 (сумма ненулевых == order)
- SyncSupplierProjectsJob и SyncSupplierProjectJob: create/missing/dead/update-циклы
  идут только по площадкам с долей >=1 (?? 0 снят — ключ гарантирован, phpstan 0 на этих файлах)
- тесты: allocator (limit-1/2, zero), новый job-тест limit-1 site -> ровно 1 save

Прогон supplier+jobs: 261/261.
NB: LEFTHOOK_EXCLUDE=larastan — гейт падал на предсуществующих ошибках чужого
tests/Feature/Sales/SalesClientsIndexTest.php (не связано с этим фиксом).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 15:51:06 +03:00
Дмитрий 4e6ac1057f docs(prod): план чистки прода + замены ЛК поставщика на crm.lead.store
Обоснование (spec) + детальный пошаговый план с полным откатом:
- факты живой базы (7 тест-клиентов, деньги синтетические), keep/delete
- разведка crm.lead.store: визуал новый, начинка (эндпоинты/API/формат лида) та же
- порядок: свап ЛК -> проверка проектов по алгоритму -> чистка кабинета -> чистка базы
- маркировка БОЕВОЙ ПРОД во всех доках + стартовый хук

Секреты/ПДн не включены (почты замаскированы, секрет вебхука как <секрет>).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 12:36:09 +03:00
Дмитрий 55c14fc7c2 feat(sales): сервис метрик клиента за период
Task 1.2: SalesMetricsService — leadsDelivered/oborotRub/topupsRub/cumulativeTopupsRub/runwayDays. Деньги: оборот из целых копеек (/100 в конце), полуоткрытые интервалы [start, след.день). runwayDays реиспользует PricingTierRepository→BalanceToLeadsConverter→RunwayCalculator (совпадает с кабинетом клиента, F3). leadsDelivered 1:1 с DashboardController (deleted_at NULL, is_test=false). Тест 15/15 (с граничными), stan 0. Один эскейп на сессию.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:46:31 +03:00
Дмитрий 07b5758291 feat(sales): резолвер периода (этот/прошлый/позапрошлый/произвольный)
Task 1.1: SalesPeriodResolver.resolve({kind,from,to})→SalesPeriodRange (МСК, Europe/Moscow) + monthsIn() для ступенчатого тарифа. Поддержка this/prev/prev2/custom; неизвестный kind→this; custom from>to→исключение. Тест 10/10, stan 0. Один эскейп на сессию.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:35:48 +03:00
Дмитрий ef0f7c803f feat(sales): каркас фронта портала — layout, роутер, вход, период-пикер
Task 0.7: SalesLayout (сайдбар ОТДЕЛ ПРОДАЖ, 2 секции nav, boss-only для head), SalesLoginView (реальные email+пароль, не демо-кнопки), сторы salesAuth/salesPeriod, api/sales.ts, PeriodPicker (этот/прошлый/позапрошлый/произвольный), HelpHint (?). Роуты /sales/* с гардом (токен→login, boss-only→/sales). Заглушка SalesStubView для экранов будущих фаз. Vitest SalesLogin 5/5, весь фронт 1005 без регрессий, type-check/lint чисто. Вёрстка по демо v8_sales.html. Один эскейп на сессию.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:27:04 +03:00
Дмитрий 86bbeb1f06 style(sales): pint-канон контроллера/маршрутов/теста входа
Lefthook-pint не перестейджит — приводим к канону версии из c366614f. Без изменений логики. Один эскейп на сессию.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:09:05 +03:00
Дмитрий c366614fcd feat(sales): вход (login/me/logout) + маршруты /api/sales
Task 0.5+0.6: SalesAuthController (login 200/422/403, me, logout) + маршруты /api/sales/auth и зона данных. Порядок middleware admin-db ДО auth:sales. Тест SalesAuthTest 7/7, весь sales-набор 25/25. Logout инвалидирует токен (в тесте Auth::forgetGuards() — артефакт мульти-запросов; в бою каждый запрос свежий). Larastan baseline: Pest false-pos SalesAuthTest. Заодно pint-канон моделей/трейта/SalesModelsTest. Один эскейп на сессию.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:07:47 +03:00
Дмитрий 372668ad41 feat(sales): EnsureSalesUser + ScopesSalesOwnership
Task 0.4: middleware EnsureSalesUser (гейт зоны /api/sales — user('sales') активен, иначе 401, alias 'sales-portal'). Трейт ScopesSalesOwnership: менеджер видит только свои tenant_id из sales_client_assignments, начальник (head) — всех (null=без ограничения). Тест SalesOwnershipScopeTest 6/6, всего sales 18/18. Larastan чистый. Один эскейп на сессию.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 12:43:31 +03:00
Дмитрий bdcb82f8f7 feat(sales): guard sales (sanctum) + provider + таблица токенов
Task 0.3: guard 'sales' (driver sanctum, provider sales_users). Тест SalesGuardTest 3/3 (valid Bearer→200, без токена→401, мусор→401). Миграция personal_access_tokens (Sanctum Bearer; раньше не было — основной кабинет SPA cookie), DDL через pgsql_supplier, гранты crm_admin_user, CHANGELOG v8.60. Larastan baseline: +SalesGuardTest (Pest TestCall false-pos), SetTenantContext int→mixed (второй provider расширил тип $request->user()). План: admin-db ДО auth:sales. Один эскейп на сессию.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 12:36:13 +03:00
Дмитрий 9b91016f46 feat(sales): Eloquent-модели портала продаж
Task 0.2: SalesUser (Authenticatable + HasApiTokens, isHead), SalesTariff, SalesClientAssignment (snapshot тарифа), SalesAttachmentRequest, SalesPayout (append-only через триггер БД). Тест tests/Feature/Sales/SalesModelsTest.php — 9 passed. Разрешение хозяина: один эскейп на сессию.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 12:08:52 +03:00
Дмитрий b0794fbef6 feat(sales): схема портала отдела продаж — 5 таблиц + append-only выплаты
Task 0.1: миграция sales_tariffs/sales_users/sales_client_assignments/sales_attachment_requests/sales_payouts (SaaS-level, без RLS, фильтр по владению в коде). Append-only триггер. Гранты crm_admin_user. DDL через pgsql_supplier. CHANGELOG_schema v8.59. Разрешение хозяина: один эскейп на сессию.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 11:57:22 +03:00
Дмитрий 5a33074dbf docs(sales): план реализации портала отдела продаж — 8 фаз, TDD, по спеке и демо
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 11:27:19 +03:00
Дмитрий 7067c583ec docs(sales): кликабельное демо портала отдела продаж + спецификация
Демо-прототип портала менеджеров по продажам в дизайне Лидерры (Forest),
приведён к реальной модели данных + расширен. Без бэкенда.

- Роли менеджер/начальник, простой демо-вход, переключение роли
- Менеджер: Сводка, Мои клиенты, карточка клиента, Привязать, Мой доход
- Начальник: Сводка отдела, Результативность, Тарифы менеджеров,
  Счета (оплата по счёту), Заявки на привязку, Выплаты, Менеджеры
- Тарифы = формула дохода (В1): 3 семейства, конструктор, переход «тариф
  прилипает к клиенту» (В12 решён)
- Выбор периода везде (этот/прошлый/позапрошлый/произвольный)
- UX-проход Playwright: ?-подсказки, починена вёрстка подписей
- Спека: docs/superpowers/specs/2026-06-28-sales-manager-portal-brainstorm.md

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 11:12:41 +03:00
Дмитрий 2d5e52799e chore(gitleaks): allowlist синтетических демо-телефонов фикстур автоподбора
Разбор 13 false-positive ru-phone-unmasked: фейк-номера в заглушке агента
(Fake*CompetitorAgent), кликабельных прототипах фичи и демо-экране DetailScreen —
не реальные ПДн, та же категория, что factories/doubles/specs. Добавлены пути
и плейсхолдер-паттерн в .gitleaks.toml. full-history скан: no leaks found.

эскейп: фиксируй (авторизовано владельцем)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 07:00:23 +03:00
Дмитрий 45d67f3322 docs(конкурентное поле): отчёт теста «тупого клиента» + план/спека + 7 скринов
Прогон фичи глазами «самого тупого клиента» на изолированной тест-БД (прод не тронут):
все формы, групповые действия, биллинг с реальным списанием, эмуляция поставки Агента, визуал.
Деньги в порядке (300/50 списываются ровно при успехе, без двойных/ложных списаний).
Найдено и ПОЧИНЕНО 6 находок (код — коммит worktree-avtopodbor 1b3683c6).
Независимая перепроверка по коду; одна самокоррекция (общий тост, не «полная тишина»).

эскейп: фиксируй (авторизовано владельцем)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 06:43:26 +03:00
Дмитрий ad519c89c8 docs(конкурентное поле): сверка прототип↔реализация + статус доводки находок
Находки эталон↔реализация и их закрытие (F1 «Предложения», F2 админ-тарифы,
F3 биллинг, M2 чистка; M1 — не баг; N1/M3 — хвосты). Код фичи — отдельным
коммитом в ветке worktree-avtopodbor (793b20a3).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 04:58:53 +03:00
Дмитрий 9737ea7b1b feat(ui): тип лица полными словами, зелёные дни недели, скрытие банк-реквизитов у физлица
- Настройки→Реквизиты и диалог проекта: «Физлицо»→«Физическое лицо», «Юрлицо»→«Юридическое лицо» (ключи value не тронуты)
- У физлица скрыт блок «Реквизиты для оплаты» (банковских реквизитов нет; оплата по счёту — только для юр/ИП)
- Диалог проекта: выбранные дни недели залиты зелёным #0f6e56 как в ProjectDetailsDrawer

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 13:22:09 +03:00
Дмитрий adfdf9583c docs(ПИЛОТ): сквозная сверка байт-в-байт прод↔git↔бэкап (1:1) после выката «оплаты по счёту»
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 13:05:12 +03:00
Дмитрий 0f1bced2a5 docs(ПИЛОТ): снимок 29.06 — фича «оплата по счёту» (Этап 1) выкачена на прод + реквизиты ИП в legal_entities 2026-06-29 11:46:38 +03:00
Дмитрий a56dcb06b2 feat(биллинг): оплата по счёту (Этап 1) — счёт, акт, отметка оплаты
Клиент сам выставляет PDF-счёт (TopupDialog вкладка «По счёту»), счета и
акты — в отдельной вкладке «Счета». Админ (/admin/invoices) отмечает оплату
одной кнопкой → атомарно зачисляет баланс (BillingTopupService), формирует
Акт (без НДС, saas_upd_documents ДОП) и шлёт клиенту письмо «Счёт оплачен»
с вложением PDF-акта. PDF открываются inline в браузере (ASCII-имя).

- Сервисы InvoiceNumberGenerator/InvoiceService/ActService/InvoicePaymentService/PdfRenderer
- Контроллеры InvoiceController (клиент) + AdminInvoiceController (список+mark-paid)
- Модели SaasInvoice/SaasInvoiceItem/SaasUpdDocument; шаблоны pdf/invoice|act
- Нумерация СЧ-ГГГГ-NNNNN (advisory-lock); просрочка invoices:expire (cron)
- Наименование услуги: «Оплата генерации рекламных лидов»
- Зависимость barryvdh/laravel-dompdf (default_font dejavu sans); схема БД не менялась
- Этап 2 (автомат через ВТБ API) — отдельно, спека/план в docs/superpowers

Тесты: счета 13, Billing 138, фронт зелёные; larastan baseline +6 (Pest false-pos).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 11:25:16 +03:00
Дмитрий f45cfb900c docs(ПИЛОТ): снимок 28.06 — сквозная сверка прод↔git↔бэкап (1:1) + Тенанты + фронт-стенд зелёный
Прод приведён к gitea-main 84dfbc85 один-в-один (rsync-checksum, потерь нет);
бэкап цел; экран Тенанты на серверную пагинацию выкачен; 992 фронт-теста зелёные;
деньги t2=1 839 405₽ целы. Остатки не-git на проде объяснены (.bak-precutover до 03.07).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 07:09:42 +03:00
Дмитрий dab91b62f7 test(фронт): привёл стенд в зелёный — 10 протухших спеков под актуальные компоненты
Все падения — устаревшие ожидания тестов (компоненты менялись намеренно):
SettingsView (роутер+вкладка Реквизиты+события), LegalDoc (реальные доки под ЮKassa),
ProjectsView (BulkActionsBar v-show→isVisible), ErrorView (убран фейк REQ/INC),
PricingTiers (формат «500 ₽»), KanbanCard (costKopecks→«—»), ChangePassword (дата из API),
DealDetail (русские ярлыки статусов), DealsView (RuDateField на v-menu), SupplierIntegration
(window.confirm→v-dialog). Изменены ТОЛЬКО тесты, компоненты не тронуты.
Полный прогон: 127 файлов / 992 теста зелёные.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 12:58:19 +03:00
Дмитрий fea4b47ecb feat(админка): экран Тенанты на серверную пагинацию/поиск/фильтры (масштаб 1000+)
AdminTenantsView грузил всех тенантов разом и фильтровал в браузере — на 1000
клиентов поиск/чипы видели только первую страницу. Теперь страница из limit/offset
+ v-pagination; поиск (ILIKE), статус (производный trial/overdue/active/suspended)
и тариф — серверные multi-фильтры. AdminTenantsController::index: statuses/tariffs
через CASE/whereIn (статус зеркалит adminTenantsMapper.deriveStatus). Опции тарифов —
отдельным запросом listAdminTariffPlans. Демо локально подтверждено.

Тесты: фронт 34/34 (tenants), бэкенд 13/13 (+2 на statuses/tariffs); baseline getJson 13→15.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 12:05:31 +03:00
Дмитрий 628423322a docs(ПИЛОТ): снимок 28.06 — починен тихо сломанный биллинг-сторож (RLS) + playwright durable
Свод за заход «закрывай хвосты»: разбор и фикс 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>
2026-06-28 11:19:02 +03:00
Дмитрий e8491e81de fix(биллинг): sweep и reminder перебирают тенантов через BYPASSRLS + playwright в зависимостях
Корень: после переезда на 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>
2026-06-28 11:09:07 +03:00
Дмитрий b2f08f28d5 feat(дашборд): Этап B+C — кликабельные группы Заказа, ссылки Здоровья, «Открыть всё»
B: строки групп «Заказа» кликабельны → проекты у поставщика (поиск по источнику) +
кнопка «Открыть проекты у поставщика». C: подсистемы «Здоровья» кликабельны →
Инциденты/Система/Интеграция с поставщиком; «Финансы» → Биллинг/Все клиенты;
«Клиенты» → Все клиенты. Сквозная вложенность дашборда замкнута до источников.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 10:23:30 +03:00
Дмитрий 6536c19c96 feat(дашборд): Этап A — сквозная вложенность Лиды до источника
Экран «Лиды» (/admin/leads): серверный список с фильтрами (дата/канал/поставщик/
статус/поиск) + пагинация (масштаб 10⁴+ лидов). Карточка лида (/admin/leads/{id}):
полная цепочка — ОТКУДА (поставщик B1/B2/B3 + канал + источник + регион) → КОМУ
(сделки клиентов через deals.source_crm_id = supplier_leads.vid). Дашборд: drill
Лиды +топ-10 последних + «Открыть все лиды →». Nav-пункт «Лиды». ПДн-телефон
маскируется (152-ФЗ). Тесты: backend 3 + FE 5 (38 FE всего зелёные).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 10:14:47 +03:00
Дмитрий 5c68b24c7b feat(дашборд): выбор периода — свой диапазон дат + спека вложенности/масштаба
Фундамент под сквозную вложенность: 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>
2026-06-28 09:54:09 +03:00
Дмитрий d961d1617a feat(админка): пункт «Командный центр» в левом меню
Дашборд не было видно в сайдбаре — уйдя с него, нельзя было быстро вернуться.
Добавлен первый nav-пункт «Командный центр» → /admin/dashboard (иконка dashboard).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 08:42:19 +03:00
Дмитрий 1ecb965981 feat(дашборд): плитка «Клиенты» — активность + новые + спящие
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>
2026-06-28 08:28:47 +03:00
Дмитрий 22ad20337a feat(балансы): баланс поставщика = остаток номеров × 20 ₽
У кабинета crm.bp-gr нет денежного баланса — есть «Баланс ГЦК» (остаток номеров)
в выпадашке шапки (table.balancetbl). supplier-balance.js логинится, раскрывает
выпадашку, читает «Баланс ГЦК» -> {numbers}. Провайдер: деньги = numbers ×
number_price_rub (20 ₽/шт, подтверждено владельцем). Live: 3096 -> 61 920 ₽.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 07:54:10 +03:00
Дмитрий fa404e98ec fix(балансы): свежий query-builder на итерацию джобы (PK violation на 2-м прогоне)
Переиспользование одного DB-билдера в цикле накапливало where-клаузы →
updateOrInsert уходил в INSERT существующей строки → SQLSTATE 23505 на проде
при повторном сборе. Билдер теперь создаётся внутри цикла. + тест на 2 прогона.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 07:32:17 +03:00
Дмитрий c03e2b319b fix(балансы): DaData X-Secret заголовок + кламп days_left к 0
- 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>
2026-06-28 07:25:21 +03:00
Дмитрий 505dd5711e docs(план): Amendment A — кнопки «Пополнить» в плитке балансов
Требование владельца 28.06: прямая ссылка оплаты у каждого сервиса в дашборде
(на планшете). topup_url — статика из конфига; YC строится из billing_account_id.
2026-06-28 07:13:21 +03:00
Дмитрий 93e8393014 feat(балансы-fe): плитка «Балансы сервисов» + drill + кнопки «Пополнить»
- 5-я плитка дашборда со светофором (worst-of сервисов, поддержка grey=нет данных)
- Drill-таблица: Сервис · Баланс · Хватит на N дней · Статус · кнопка «Пополнить»
- Кнопка «Пополнить» (target=_blank) → страница оплаты сервиса; YC — прямо на биллинг
- Клиент getDashboardBalances + типы; Vitest 12/12 (тайл, drill, href кнопки)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 07:12:45 +03:00
Дмитрий 88e816c576 feat(балансы): backend плитки балансов внешних сервисов
Ежедневный контроль баланса DaData/Поставщик/Yandex Cloud плиткой дашборда.

- Таблица external_service_balances (pgsql_supplier, BYPASSRLS, last-value upsert)
- BalanceHealth: чистая логика светофора (red <floor или <3д; amber <floor или <7д)
- BalanceProvider+DTO; провайдеры DaData(API)/YC(OAuth→IAM→billing)/Supplier(Playwright)
- RefreshExternalBalancesJob: изоляция провайдеров (try/catch), расписание 06:30 МСК
- AdminDashboardController::balances() + плитка в summary + topup_url (кнопка «Пополнить»)
- Тесты: BalanceHealth, 3 провайдера, джоба, endpoint (102 теста зелёные)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 07:12:14 +03:00
Дмитрий 95ea4b764e docs(план): реализация «Балансы внешних сервисов» (дашборд)
План 12 задач (TDD): таблица external_service_balances, чистый BalanceHealth,
3 провайдера (DaData API, Yandex Cloud OAuth→IAM→billing, Supplier через Playwright),
ежедневная джоба, эндпоинт+плитка+drill, тесты, выкат. Доступы готовы на проде
(DaData ключ, YC OAuth в .env, Supplier Playwright). Supplier-баланс — разведка
селектора кабинета первым шагом.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 05:39:51 +03:00
Дмитрий e17433e069 docs(спец): дизайн «Балансы внешних сервисов» (дашборд)
Ежедневный контроль балансов 3 внешних сервисов (Поставщик crm.bp-gr, DaData,
Yandex Cloud) плиткой в Командном центре: баланс + «хватит на N дней» + светофор
(пороги ₽ и дни). Адаптер на сервис, ежедневная задача, таблица
external_service_balances, чистый BalanceHealth. Unisender убран (почта = Yandex SMTP).
Брейншторм одобрен владельцем 28.06.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 05:05:05 +03:00
Дмитрий f30c6612c0 fix дашборд: достоверность метрик (здоровье/лиды/заказ) + периоды 60/90д
По сверке прод-данных с реальностью (часть чисел вводила в заблуждение):
- Финансы: +периоды 60 и 90 дней (крупные пополнения старше 30д теперь видны).
- Здоровье: «инциденты» больше не считают авто-лог ошибок джоб (summary
  'Автоматически:%') — раньше копилось 975 и держало красный ложно. Теперь:
  open_incidents = только реальные; добавлен job_errors_24h (повторяющиеся
  ошибки джоб за сутки) в подсистему queues.
- Лиды: убраны обманчивый «% доставки» (это было «обработано», не доставлено)
  и «нераспределённые по менеджерам» (менеджеры не используются). Добавлено
  «получено от поставщика сегодня»; доставлено = реально созданные сегодня сделки.
- Заказ: показаны дата снимка и полная картина (всего активных заказов /
  Σ лимита у поставщика) — сверка по снимку больше не выглядит занижено.

Тесты: admin-срез 87 зелёных, unit 3/3, фронт 10/10. stan 0, pint/eslint/
type-check/build чисто.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 18:57:35 +03:00