Commit Graph

349 Commits

Author SHA1 Message Date
Дмитрий 6b26bf28a7 feat(brain): построение индекса ниш из карточек + CLI-пересборка
index_store.py сканирует папку карточек, считает отпечаток каждой (embed_fn
инъекцией) и складывает индекс nisha/roditel/sinonimy/path/vector;
save_index/load_index — JSON roundtrip. build_index.py — CLI ручной
пересборки индекса (провайдер эмбеддингов через ALEX_EMBED). TDD: тест
падал на ModuleNotFoundError до реализации, затем 2 passed (плюс полный
пакет brain — 12 passed, регрессий нет).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 08:20:19 +03:00
Дмитрий 0df125c855 feat(brain): парсер карточки + сборка справки по дереву домен→ниша 2026-07-17 08:06:32 +03:00
Дмитрий f3b445b049 docs(brain): спека + план — хранилище знаний Александры (Obsidian) + умный поиск ниши 2026-07-17 07:56:06 +03:00
Дмитрий 1c3da4a25e docs(golos): прогресс телефонии Mango (бот REGISTERED) + рестарт-промт под компакт
Телефония: бот регистрируется в Mango (pyVoIP 1.6.8, PhoneStatus.REGISTERED, NAT-aware rport).
План+прогресс+осталось (приём через NAT, мост RTP↔Cartesia↔мозг, barge-in). Рестарт-промт под
оба трека. Секреты и ПДн (SIP-пароль, номера, ключи) — вне git, замаскированы.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 05:25:52 +03:00
Дмитрий ec903c6d19 docs(golos): чистка голоса (бренд-IPA/нервность/угу) + план телефонии Mango
Голос: fix_pron() лечит бренд «Лидерра» через IPA-блок Cartesia (подтверждено на ухо);
нервничание с затуханием (speed+calm/content, т.к. emotion anxious на этом голосе — пустышка);
угуканье подрезано. Всё принято владельцем на слух.
Телефония: план стройки под ключ (Mango SIP vpbx400046720.mangosip.ru → медиа-мост на GPU →
уши/голос Cartesia + barge-in). Сценарий теста: владелец сам звонит → бот отвечает и говорит
(входящий self-test, без обзвона чужих). Креды/скрины — вне git.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 00:33:06 +03:00
Дмитрий 57f1ca3900 docs(golos): движок Александры — наука телефонных продаж + плавающий темп + лестница финала (снимок движка + журнал)
Вшита наука телефонных продаж (Gong/Chorus, Восс/SPIN/Sandler): слушать>говорить,
SPIN-лесенка, зеркало/называние чувства, стоп-слова, дисциплина цены. Плавающий темп
Cartesia (0.86/0.90/0.95, только версия 2025-04-16). Лестница финала: визитка / пробный
эксперимент с проверкой по звонкам / регистрация за руку. Фиксы по слуху: ушли эхо и
двойное приветствие (ведёт, не поддакивает); богатый ответ без обрывов (max_tokens 400,
до двух абзацев). Снимок движка aleksandra_engine.py — durable, без ключей.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 23:23:26 +03:00
Дмитрий 87b7f4117f docs(golos): рестарт-промт под движок Александры (полный контекст для новой сессии)
Переписан paste-блок восстановления контекста под текущее состояние: движок
собран под ключ (живость + продающая база + отраслевая призма + живой Sonar +
жёсткий разворот), новый порядок чтения (журнал → паспорт → база), боевые
розетки, ключи/квирки, следующие шаги, стиль владельца.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 22:12:04 +03:00
Дмитрий 9317974670 docs(golos): движок Александры под ключ + живой Perplexity(Sonar) + жёсткий разворот ниши
- жёсткий разворот ниши (стоматология → займы под залог): бот ловит смену,
  тормозит разведочным вопросом (= покупка времени), свапает справку,
  додавливает через призму новой ниши; гарантий не даёт (флажок держит).
- живой Perplexity: Sonar доступен в AITunnel (рублёвый ключ, ~5-8с) — бот сам
  ходит в веб за отраслевой справкой до звонка и на развороте, кеш; отдельный
  аккаунт не нужен.
- движок собран в один файл scratchpad/aleksandra_engine.py (мозг+характер+
  живость+отрасль), прогнан end-to-end с живым Sonar. Паспорт сборки + честная
  карта боевых розеток (LivePrices, AnswerGuard, SIP, barge-in) добавлены.
- журнал и рестарт-файл обновлены.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 22:07:34 +03:00
Дмитрий 1a4218e1ee docs(golos): продающая база вплетена в живой круг (Александра продаёт из базы)
zhivoy_krug_prodazhnik.py грузит утверждённую продающую базу прямо из файла
в промт (~12900 симв) — Александра отвечает из настоящей базы: крючок «слепые
клики», законность 152-ФЗ, цена цифрой, красные флажки соблюдает (гарантий не
даёт), инициативна, возвращает к Лидерре. Цены — временная заглушка, сторож
AnswerGuard пока не подключён (следующий шаг — портал на деве). Журнал и
рестарт-файл обновлены.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 20:04:50 +03:00
Дмитрий 27644d6adc docs(external): план реализации чистки доп-каналов поставщика (TDD, 5 задач)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 19:15:37 +03:00
Дмитрий 907bd2f231 docs(external): спека модуля чистки доп-каналов поставщика (B6/B8 → удаление в 20:00, KEEP=B1/B2/B3)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 19:15:37 +03:00
Дмитрий b1814bea34 feat(external): POST /balances/refresh принимает services[] (групповое обновление) 2026-07-16 17:05:38 +03:00
Дмитрий d4b466cf75 feat(external): keyso — проба живости (зависимость поиска клиентов, 13-й ключ) 2026-07-16 17:03:47 +03:00
Дмитрий c1f46c0cc5 refactor(external): xfetch — проба живости (ключ у sales-finder, не у портала) 2026-07-16 17:00:14 +03:00
Дмитрий c1f015d17d feat(external): ExternalBalanceRefresher — обновление набора сервисов + окно свежести + BalanceReading::alive()
Фаза A плана 2026-07-16-external-services-online-monitoring:
- BalanceReading::alive() — состояние «жив, денежного баланса нет».
- ExternalBalanceRefresher: реестр 6 сервисов, refresh(keys, force, sendAlerts),
  окно свежести 60с, edge-trigger алерта по колонке light (фон/кнопка sendAlerts=false молчат).
- RefreshExternalBalancesJob делегирует рефрешеру (поведение суточного сбора сохранено).
Тесты: 35 External зелёные, Larastan 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 14:50:01 +03:00
Дмитрий a29f10a25f fix(sales): «Мой доход» падал у менеджера (permission denied на sales_client_assignments)
Аутентифицированный sales-user грузится под pgsql (crm_app_user) до admin-db
(приоритет middleware Authenticate > кастомного UseAdminConnection), и модель
запоминает это подключение. SalesEarningsService лениво читал $u->assignments/
->currentTariff → запрос уходил в crm_app_user (нет прав на sales_*) → 42501.
Фикс: assignmentsOf()/tariffOf() — свежие запросы на текущем (admin) подключении,
как уже делает SalesMetricsService. Регресс-тест: bogus-подключение на модели.
Гейты: 18/18, Larastan 0. Тесты грант НЕ ловят (общий admin-PDO) — ловится живьём.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 09:37:47 +03:00
Дмитрий b571445731 style(sales): Pint — импорт Connection в SalesProspectsAdvanceJob
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 19:10:27 +03:00
Дмитрий a2fa9ceb15 fix(sales): сервис-канал поиск→портал — исключить из CSRF (POST давал 419)
Интеграционные роуты в web.php → web-группа применяла VerifyCsrfToken; у
Python-поиска нет сессии/куки, POST /integration/prospects падал 419 (в бою).
Pest это не ловил (Laravel гасит CSRF под тестами). Защита канала — сервис-токен.

Найдено живым смоуком после выката Этапа 2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 18:28:49 +03:00
Дмитрий fa0f79b69e feat(sales-finder): Этап 2 — кнопка «Отдать менеджеру», колонка Менеджер, клиент портала
В поиске: выпадашка менеджеров (из портала) + кнопка «Отдать менеджеру» рядом
с «Собрать контакты», колонка «Менеджер» перед «Оценкой», таблица с отступом.
Новый salesfinder/portal.py (get_managers/send_prospects, полный payload фирмы),
поля Firm.assigned_manager_id/name. Портал недоступен → кнопка disabled, ошибка
не помечает строки. Гейты: 244/244 pytest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 18:22:15 +03:00
Дмитрий 16aa00a0e4 feat(sales): Этап 2 — сервис-канал поиск→портал (managers + ingest)
Портал публикует /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>
2026-07-15 18:13:59 +03:00
Дмитрий ad2c61bab5 docs(sales): замечания владельца после демо Этапа 1 (карточка=все поля, сорт по созвону)
3 правки в §15: карточка показывает ВСЕ данные поиска (payload), фильтр —
конкретный выбор менеджера, сортировка «Переговоры» по ближайшему созвону.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 17:16:38 +03:00
Дмитрий 5fb20a1318 docs(sales): миграция — добавил testing в список стадий в шапке (RLS-review)
Этап 1 Task 15. Комментарий отставал от CHECK (8 стадий). Консистентность.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:50:57 +03:00
Дмитрий 869590a1b2 feat(sales): маршруты + пункты меню воронки (менеджер/начальник)
Этап 1 Task 14. /sales/prospects (менеджер), /sales/prospects/board
(начальник, salesBossOnly). Пункты «Потенциальные клиенты» и «Воронка отдела».

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:46:54 +03:00
Дмитрий 4b5afacfb7 feat(sales): экраны воронки — менеджер (свои) + начальник (все + фильтр)
Этап 1 Task 12–13. SalesProspectsView (менеджер, listProspects свои),
SalesProspectsBoardView (начальник, listSalesManagers фильтр, все карточки).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:44:29 +03:00
Дмитрий 4212380d6c feat(sales): доска-канбан прогнозов (8 колонок, клик→диалог, просрочка)
Этап 1 Task 11. Без drag: карточки раскладываются по стадиям, клик эмитит
open, просроченный next_call_at подсвечивается.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:42:08 +03:00
Дмитрий bb2fe2f34d feat(sales): диалог карточки прогноза — результат разговора + валидация
Этап 1 Task 10. Селектор результата, поля по стадии, отказ скрыт при user,
обязательные причина/время. Тесты через vm (v-dialog телепортит контент).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:40:46 +03:00
Дмитрий ead1335242 feat(sales): фронт — API прогнозов + утилита стадий/просрочки
Этап 1 Task 9. PROSPECT_STAGES (8 колонок), stageMeta, isOverdue;
listProspects/updateProspect в api/sales.ts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:38:07 +03:00
Дмитрий d6cddf73a6 feat(sales): artisan sales:prospects-demo — демо-карточки воронки
Этап 1 Task 8. По 2 карточки на каждую из 8 стадий у указанного менеджера
(для показа досок). Baseline под artisan() Pest-паттерн.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:35:52 +03:00
Дмитрий 9d8fd8d48c feat(sales): API воронки — список (свои/все+фильтр) + результаты разговора
Этап 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>
2026-07-15 16:33:29 +03:00
Дмитрий 4b7463d1d9 feat(sales): модель SalesProspect + фабрика
Этап 1 Task 2. Дефолт stage=new в $attributes (доступен до refresh).
ide-helper мисин добавлен в локальный стаб (регенерация всех моделей).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:24:53 +03:00
Дмитрий 4311fd8df9 docs(sales): план Этапа 1 «Потенциальные клиенты» — доски в портале
15 задач по TDD: миграция sales_prospects, модель, GET/PATCH API (свои/все+
фильтр, результаты переговоры/недозвон/отказ с правилами), демо-команда, доска-
канбан + диалог карточки + экраны менеджера и начальника + меню/маршруты.
Этапы 2 (поиск→портал) и 3 (автожизнь) — отдельными планами.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:03:24 +03:00
Дмитрий cbed285288 docs(sales): дизайн «Потенциальные клиенты» — воронка-канбан отдела продаж
Начальник отдаёт фирмы из поиска менеджеру → канбан в кабинете портала.
7 колонок (Новые/Переговоры/Зарегистрировался/Пополнил баланс/Пользователь/
Отказ/Недозвон), движение карточки — по результату разговора (без drag),
автостадии по реальным пополнениям, интеграция поиск→портал по сервис-токену.
Общая доска начальнику + фильтр по менеджерам. 3 этапа. Согласовано в чате.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 15:55:00 +03:00
Дмитрий ad50861ed5 fix(sales): тариф не сохранялся — первая ступень «0—1» (from=0) отклонялась валидацией
Форма «Тарифы менеджеров» дефолтит первую ступень с 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>
2026-07-15 15:15:10 +03:00
Дмитрий 025bd98ea8 chore(security): phpstan-baseline в allowlist gitleaks — генерённый файл цитирует тест-фикстуры
Перегенерированный 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>
2026-07-14 18:10:21 +03:00
Дмитрий 915dfbc8fc docs(ПИЛОТ): снимок 29.06 — фича «оплата по счёту» (Этап 1) выкачена на прод + реквизиты ИП в legal_entities 2026-06-29 11:47:34 +03:00
Дмитрий 1573fda126 docs(ПИЛОТ): снимок 28.06 — сквозная сверка прод↔git↔бэкап (1:1) + Тенанты + фронт-стенд зелёный
Прод приведён к 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>
2026-06-29 07:10:10 +03:00
Дмитрий 94043ea6ed 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:59:01 +03:00
Дмитрий fc58c96fe4 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:24 +03:00
Дмитрий be363695fd 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:11:36 +03:00
Дмитрий 97c3a6f791 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
Дмитрий 76df5ce703 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
Дмитрий 6d313b0fbd 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
Дмитрий 3581bd41ae 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
Дмитрий 2959605d2a 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
Дмитрий 63d6b88d86 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
Дмитрий 6bc511c63d 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
Дмитрий 4cb9bfa9d7 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
Дмитрий a6023e1960 docs(план): Amendment A — кнопки «Пополнить» в плитке балансов
Требование владельца 28.06: прямая ссылка оплаты у каждого сервиса в дашборде
(на планшете). topup_url — статика из конфига; YC строится из billing_account_id.
2026-06-28 07:13:21 +03:00
Дмитрий 99e8eba227 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
Дмитрий 2ac6586a24 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