Commit Graph

183 Commits

Author SHA1 Message Date
Дмитрий 56f06ac2c3 fix(портал-мобилка): деталь сделки на телефоне — на весь экран
Панель деталей сделки была фикс. ширины (inline 400px в «Сделках», drawer 480px
в Канбане) — на экране 390 она была шире экрана: в «Сделках» давала горизонтальный
скролл страницы и крошила карточки списка, в Канбане левый край уезжал за экран.
На телефоне:
- «Сделки» — inline-панель стала полноэкранным оверлеем (position:fixed inset:0).
- Канбан — ширина drawer = ширине экрана (useDisplay).
Десктоп/планшет — прежний master-detail 400px / drawer 480px, без изменений.
Тесты 103/103 зелёные.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 20:56:42 +03:00
Дмитрий cdd0237108 fix(портал-мобилка): диалоги на телефоне — на весь экран (fullscreen)
Узкие центрированные модалки на телефоне резали содержимое (ряды кнопок,
вкладки, дни недели, сетку регионов — контент вылезал за края, текст обрезался
слева и справа). Диалоги с богатым содержимым теперь на телефоне открываются
на весь экран (:fullscreen по useLayoutTier + scrollable):
- Проект (создание/редактирование) — NewProjectDialog
- Новая сделка — NewDealDialog
- Пополнение баланса — TopupDialog
- Дни оптом — DaysBulkDialog (те же кнопки дней)
- Регионы оптом — RegionsBulkDialog
- Импорт: сопоставление статусов — UnknownStatusesDialog
Десктоп/планшет — прежний центрированный модал, без изменений.
Тесты диалогов 59/59 (+2 skip) зелёные.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 20:46:47 +03:00
Дмитрий 23c4179aff fix(портал-мобилка): небольшой зазор у «Поиска» в шапке на телефоне
При длинном заголовке («Конкурентное поле») кнопка «Поиск» стояла вплотную
к тексту (зазор 0). Добавлен margin-left 10px на телефоне (media <600px) —
теперь есть воздух. Десктоп/планшет без изменений.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 20:09:18 +03:00
Дмитрий f9e589497e fix(биллинг): метка для списаний автоподбора в истории транзакций
Списания type=autopodbor_charge шли без описания и показывались как «—»
и в таблице (десктоп), и в карточках (телефон). Добавлена метка
«Списание за автоподбор» в общий словарь операций TransactionsTable.

Тесты транзакций 9/9 зелёные.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 19:58:48 +03:00
Дмитрий 6b8f8da019 fix(портал-мобилка): Дашборд — переключатель периода во всю ширину на телефоне
На телефоне v-btn-toggle (Сегодня/7 дней/30 дней/Период) растянут на всю
ширину с равными кнопками — убрана внутренняя горизонтальная прокрутка.
Только media-запрос <600px; десктоп/планшет без изменений.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 19:46:55 +03:00
Дмитрий f0f98ad152 feat(портал-мобилка): планшет — узкая колонка-иконки (rail)
На планшете (600–1279px) сайдбар сжимается в rail: только иконки пунктов,
подписи/поиск/бренд-текст скрыты, контент шире. Иконки берутся из mdi,
уже заложенных в данные пунктов; на десктопе иконки скрыты — текстовый
сайдбар остаётся прежним. Отступы топбара и контента разведены по ярусу
(десктоп 232/240px, планшет 64/72px, телефон 0). Гамбургер топбара теперь
показывается только на телефоне (d-sm-none) — на планшете/десктопе он не нужен.

Тесты каркаса 24/24 зелёные. Проверено на 820 и 1440px; десктоп не изменился.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 19:42:50 +03:00
Дмитрий c400dcbc4b feat(портал-мобилка): «Биллинг» — карточки транзакций на телефоне
История транзакций обёрнута в ResponsiveTable: на десктопе/планшете —
v-data-table-server как прежде, на телефоне — карточки (операция, сумма
цветом, дата, #ID) + компактный пейджер по 20. Серверная пагинация сохранена.

Тесты биллинга 40/40 зелёные. Проверено на 390 и 1440px.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 19:17:17 +03:00
Дмитрий 73a5096f6f feat(портал-мобилка): ResponsiveTable + карточки «Сделок» на телефоне
Обёртка ResponsiveTable (таблица на десктопе/планшете, карточки на телефоне
по ярусу useLayoutTier). Применена к реестру лидов «Сделки»: на телефоне —
карточки лида (чекбокс выбора, телефон, статус-плашка, источник·город,
комментарий, дата), тап открывает карточку сделки; на десктопе прежняя
v-data-table без изменений.

Тесты: ResponsiveTable.spec (2), «Сделки» 58/58 зелёные. Проверено на 390 и 1440px.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 19:17:01 +03:00
Дмитрий 1140fd0c09 feat(портал-мобилка): каркас — сайдбар прячется на телефоне, снизу панель-вкладки и «Ещё»
AppLayout: ярус-отступ (десктоп/планшет 232px, телефон 0 + низ под панель), рендер
AppBottomNav/AppMoreDrawer на телефоне, гамбургер открывает «Ещё». AppSidebar:
проп tier + скрытие на телефоне. Десктоп/планшет без изменений.
2026-07-07 17:59:04 +03:00
Дмитрий bc77c84e4c feat(портал-мобилка): AppMoreDrawer — выезжающее «Ещё» на телефоне
Разделы вне 5 нижних вкладок: Дашборд, Отчёты, Настройки (реквизиты), Импорт, Помощь.
Ни один экран не выкидываем.
2026-07-07 17:54:00 +03:00
Дмитрий 5e39c50fa0 feat(портал-мобилка): AppBottomNav — нижнее меню-вкладки на телефоне 2026-07-07 17:50:36 +03:00
Дмитрий b0bd110f8f fix(projects): регионы в 4 выпадашках раздела «Проекты» — сортировка по сути названия
Те же выпадашки региона (новый проект, фильтр списка, массовое
add/remove регионов, карточка проекта) переведены на REGIONS_ALPHA —
единый порядок с автоподбором, республики по своему имени, а не кучей.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 16:30:42 +03:00
Дмитрий 0f30edb2b6 Merge remote-tracking branch 'gitea/main' into worktree-avtopodbor
# Conflicts:
#	app/config/services.php
#	app/tests/Pest.php
2026-07-06 14:50:58 +03:00
Дмитрий 8cc9ea122b deploy(prod): launch-gate ЯДРО на liderra.ru — баланс блокирует запуск, не создание
Ядро фичи для обычных проектов (без автоподбора): единый LaunchBalanceGate под
lockForUpdate(Tenant), ProjectService::create($launch)+setActive, bulk resume
«сколько влезло», ProjectController store/update/toggle, config/billing.php,
ProjectResource.preflight_blocked_at + фронт экранов проектов (сообщения в рублях,
метка «не запущен», группы). Схема БД не менялась (preflight_blocked_at уже был).
Cherry-pick ТОЛЬКО ядра поверх main 73b56042; автоподбор НЕ выкатывается.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 13:09:38 +03:00
Дмитрий d15a7cfd93 feat(биллинг-фронт): баланс на запуске — сообщения в рублях, метка «не запущен», группы
Фронт для правила «баланс блокирует запуск, а не создание»:
- ProjectLimitOverloadDialog переформулирован в «Проект не запущен — пополните
  ~X ₽ или уменьшите объём» (сумма в рублях topup_rub).
- NewProjectDialog: создание всегда успешно; при launch.deferred показывает
  сообщение (не блок). Update-лимита 409 → тот же диалог.
- Возобновление (toggle-active) ловит 409 balance_insufficient (стор не
  переключает is_active), показывает диалог; постоянная метка «Не запущен —
  не хватает баланса» на карточке/в панели по preflight_blocked_at/balance_blocked.
- BulkActionsBar: сводка «Запущено N, отложено M — не хватает баланса» на resume.
- Автоподбор: CreateScreen показывает сводку запуска (рубли); FieldCompetitor
  массовые pause/resume через общий POST /api/projects/bulk (наследует BULK_MAX,
  слепок-защиту, балансовый гейт).
- ProjectResource отдаёт preflight_blocked_at.

Новые/обновлённые фронт-тесты зелёные. Пред-существующий красный baseline
фронт-набора (ErrorView/Settings/Legal/… + ProjectsView v-show) не трогался.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 12:04:16 +03:00
Дмитрий 77dda5964b feat(конкурентное поле): доводка фронта до прототипа — F1/F2/F3 + чистка M2
Сверка прототипа с реализацией показала расхождения — закрыты по TDD (dev, фронт):

- F1: экран «Предложения» (FieldProposalsScreen) переписан под вид «Поля» —
  карточки-плитки field-shared, тип+«предложение», крупная похожесть, Сайт +
  Справочник 2ГИС·Яндекс, править/удалять в карточке, массовый перенос; кнопка
  «Собрать конкурентов» открывает единое окно сбора 300 ₽ вместо старого autoform.
- F2: новый дружелюбный админ-экран AdminAutopodborPricingView (правка цен
  доп.услуг через PUT /api/admin/system-settings/{key} с обоснованием для аудита,
  сетка лидов для справки) + маршрут /admin/autopodbor-pricing + пункт меню.
- F3: колонка «когда списывается» в панели доп.услуг биллинга.
- M2: удалён мёртвый экран FieldManualCompetitorScreen (+ спека) — на него не
  было переходов; ручное добавление живёт окном на «Поле».

Тесты автоподбор+админ 43/43 зелёные, продакшен-вёрстка eslint-чистая, vite build .
НЕ на проде. M1 (18:00/21:00 МСК) — не баг, реальный инвариант продукта, не трогал.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 04:57:58 +03:00
Дмитрий ab1c048ded feat(Конкурентное поле): рабочее место конкуренты→источники→проекты (поверх автоподбора)
Фича «Конкурентное поле» на dev до уровня прототипа 2026-06-29-konkurentnoe-pole-proto.html.

Данные: box (proposal|field) на competitors+sources; phone_type city/mobile/tollfree рядом
с phone_kind (вариант C). 3 миграции, дефолты тарифов 300/50.

API (AutopodborController): GET /field (+счётчики), GET /proposals, PATCH/DELETE competitors
и sources с гвардами активного проекта, переключение box, POST /competitors/manual (+directory_urls),
competitor(id) обогащён box+project-статусом; projectStatus отдаёт limit/delivered/days/regions.
Смена источника проекта = PATCH /api/projects/{id} (реальный гвард слепка §14.10).

Фронт: FieldWorkspaceScreen/FieldCompetitorScreen/FieldProposalsScreen/FieldManualCompetitorScreen
+ field-shared.css (Forest) + AutopodborServicesPanel в Биллинге. Дословно по прототипу: подзаголовки,
баннер предложений, баннер правил времени 18:00 МСК, Справочник 2ГИС·Яндекс, статус проекта
5/день·заявки, окна сбора с ценами 300/50 + «что известно», полные формы. Пункт меню «Конкурентное поле».

Тесты: backend автоподбор 80/80, фронт автоподбор 49/49. Движок шага 2 = заглушка FakeCompetitorAgent.
OmegaDemoFieldSeeder — только для визуальной проверки (НЕ на прод).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 04:18:46 +03:00
Дмитрий 3b5990645a 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:32:21 +03:00
Дмитрий 26c75b23fd feat(автоподбор): фронт — каркас экрана, вход, роут, пункт меню
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 15:10:29 +03:00
Дмитрий aa0d595f67 refactor(fe): снять ручные обходы меню - заменены глобальным установщиком
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 10:27:46 +03:00
Дмитрий 3efe3bff27 fix/projects: смена лимита-региона-дней на защищённом проекте больше не блокируется ложно как смена источника
Симптом: на проекте, по которому уже идут лиды от поставщика, правка только лимита, региона или дней отдавала 422 «Изменить источник можно будет после N» — хотя источник не менялся. Найдено приёмкой 25.06.2026 глазами через Playwright. Дефект на main, то есть живой на боевом liderra.ru.

Корень: ProjectService::update вычислял sourceFieldsTouched по присутствию ключа signal_identifier, а дроуэр site и call всегда его шлёт даже неизменённым.

Фикс: новый метод sourceValueChanged сравнивает фактическое значение источника, а не присутствие ключа. Guard срабатывает только на реальную смену источника.

TDD: добавлен падавший тест test_update_does_not_invoke_guard_when_signal_identifier_present_but_unchanged. Larastan чист, phpstan-baseline обновлён под Mockery-шум. Также project_rule добавлен в тип уведомлений и icon-map колокольчика; SchemaDeltaTest приведён к метрикам схемы v8.55 после 2 новых таблиц.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 03:34:55 +03:00
Дмитрий 2ce16436e0 fix/ui: объявления о датах — актуальны по времени суток (правило 18:00/21:00 МСК)
Найдено проверкой глазами в 19:27 МСК (после 18:00):
1. Баннер правок количества/региона/дней говорил «вступят со следующего дня» — врал
   после 18:00 (правка не попадает в сегодняшний слепок → реально послезавтра). Теперь
   показывает АКТУАЛЬНУЮ дату через firstLeadDate (до 18:00 → завтра, после → послезавтра):
   «…вступят в силу с 27 июня». Дроуэр + окно «Редактировать».
2. Сообщение блокировки удаления/смены источника в SupplierSnapshotGuard было захардкожено
   «мы увидим это сегодня в 18:00 … можно будет послезавтра» — после 18:00 «сегодня в 18:00»
   уже прошло. Теперь time-aware через computeGraceUntil: «…лиды придут до 26 июня … можно
   будет после 26 июня».

Проверено глазами: баннер лимита (27 июня), подтверждение источника (до 26 июня),
блок удаления (после 26 июня) — все согласованы и меняются по времени суток. Тесты:
guard 30/30, фронт 38/38, leadDate (18:00 порог) зелёные.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 19:40:04 +03:00
Дмитрий de4872a6b3 feat/ui: текст правила смены источника — единый из API, без дублей в JS (Эпик 6.3)
ProjectResource.source_change_message = ProjectRuleMessages.sourceChanged (тот же текст,
что in-app уведомление 6.2). Диалоги подтверждения (дроуэр + окно Редактировать) тянут его
из API с fallback на локальный текст. Бэкенд — единственный источник строк правил, экран и
колокольчик не расходятся. Проверено глазами (epic6-unified-rule-text-confirm.png). Тесты:
ProjectResource 5/5, дроуэр 27/27, EditProjectDialog 7/7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 19:08:47 +03:00
Дмитрий 6122c553aa feat/ui: смена источника — объявление + подтверждение вместо жёсткого замка (Эпик 3)
Редизайн UX (design-gate 2026-06-25): раздача доводит хвост по старому источнику
через слепок, поэтому замок не нужен.
- 3.1: поля источника больше НЕ disabled; при правке количества/региона/дней на
  залоченном проекте — информ-баннер pdd-applies-from-banner (вступит со след. дня).
- 3.2: смена источника на залоченном проекте → диалог подтверждения
  pdd-source-change-confirm; PATCH только после подтверждения.
Старые тесты замка (disabled-поля) переписаны под новое поведение. 26/26 зелёных.

⚠️ Не выкатывать раньше включения флага routing_match_by_snapshot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 17:48:34 +03:00
Дмитрий f6c002b3db feat/ui: подсказки вопросик по карте — дашборд, мастер проекта, биллинг
Дашборд: вопросик у KPI Получено/Конверсия/Активные, у воронки и у прогноза хватит на N дней; pp на п.п.
Мастер проекта: подпись лимита заявок в день + вопросик у выбора источника Сайт/Звонок/СМС.
Биллинг: вопросик у Цены за лид 7 ступеней.
Тест FunnelChart 8/8, type-check чистый, затронутые спеки 58/60.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 13:41:27 +03:00
Дмитрий 44a18c2e3d fix/deals: реальное время сделки в шапке карточки V1 вместо захардкоженных 28 мин
Шапка показывала formatRelative(28) — всегда 28 мин назад у любой сделки.
Теперь deal.receivedMinutesAgo. Тест DealDetailHero 13/13.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:54:32 +03:00
Дмитрий cc6a33607e feat/onboarding: приветственный тур при первом входе Фаза 3
Лёгкий тур без сторонних зависимостей: подсвечивает пункты меню по порядку
пополнить→создать проект→где заявки→помощь. Показывается один раз localStorage,
можно пропустить. Якоря data-tour на AppSidebar, монтируется в AppLayout.
Тест WelcomeTour 4/4. NB: пиксельное позиционирование проверить визуально на выкате.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:36:15 +03:00
Дмитрий bddd2a8d7d fix/dashboard: нейтральное приветствие без коллега когда имя не задано U2
Без имени дашборд звал коллега — звучит как к чужому. Теперь Доброе утро! без обращения;
имя клиент задаёт в Настройки Профиль. U6 автопополнение уже имел тултип-пояснение.
Тест DashboardPageHead 6/6.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:28:38 +03:00
Дмитрий ac380d99b7 fix/runway: единый прогноз дашборд↔биллинг при нет активных проектов B1-2
Раньше при 0 активных проектов дашборд показывал хватит на 0 дней при полном балансе,
а биллинг — Запас бесконечность. Унифицировано: оба показывают нет активных проектов
прочерк. Бэкенд дашборда больше не приводит null к 0; фронт рисует null как нет проектов.
Заодно поправлен пред-существующий красный тест 28 дня на верную форму 28 дней.
TDD: DashboardSummaryTest 11/11, фронт BalanceCard/Dashboard/Billing 27/27.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:21:55 +03:00
Дмитрий d8f13cbe87 fix/ui: убрать жаргон в клиентском UI — без конкурент/синхронизация/crm.bp-gr.ru/Pay-per-lead
U1 остаток: пояснения источника проекта без слова конкурент.
U5: баннер списка проектов про результат а не синхронизацию + статус Собирает заявки.
Деалы/импорт/логин: убрано имя поставщика и англ Pay-per-lead.
Активность сделки: технический supplier_webhook заменён понятной фразой.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:02:52 +03:00
Дмитрий 82ddb762d9 fix/projects: понятные подписи источника вместо домен конкурента и донор
U1 из прогона тупой пользователь 24.06: site/call поля формы проекта
получили человеческие подписи и подсказки вместо жаргона донор/конкурент.
Затронуты NewProjectDialog.vue и ProjectDetailsDrawer.vue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 05:16:11 +03:00
Дмитрий 2fbc612827 fix/projects: косяк 06 — окно перегрузки на «вы» + кнопка «Пополнить баланс»
Единственное место с обращением на «ты» переведено на «вы» (У вас…,
пополните/поставьте/уменьшите). В окно добавлена кнопка «Пополнить баланс»,
которая закрывает окно и ведёт в /billing (онлайн-оплата ждёт Б-1, но вход
в пополнение теперь под рукой). Логику/цифры не трогал.
vitest: текст-на-вы + переход в /billing, 7 passed. Проверено глазами на 8000.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 16:20:59 +03:00
Дмитрий 879c2e7142 fix/projects: нормализация телефона-источника + понятная ошибка и подсказка
Косяк 02: поле телефона проекта типа call отвергало +7.., 8.., пробелы и скобки.
prepareForValidation в StoreProjectRequest и UpdateProjectRequest приводит номер
через PhoneNormalizer к канону 7XXXXXXXXXX без ведущего плюса, чтобы раздача
LeadRouter матчила без плюса. Финальная regex оставлена страховкой.
Кастомные messages по signal_type: ошибка с примером формата, без имени Источник.
Фронт: постоянная подсказка под полем в NewProjectDialog и ProjectDetailsDrawer.
TDD: ProjectPhoneNormalizationTest 8 кейсов, GREEN. Проверено глазами на 8000.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 13:04:58 +03:00
Дмитрий 99aa864800 feat: публичные юр-страницы оферта/политика/возврат + реквизиты + страница цен под модерацию ЮKassa 2026-06-24 09:31:52 +03:00
Дмитрий 696e75b9ac fix/projects: O — панель правки показывает блок при 409 (не молчит)
Боковая панель 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>
2026-06-23 11:44:06 +03:00
Дмитрий 18ce733f62 feat/billing: H — метка «Приостановлен — не хватает баланса» на карточке проекта
Заблокированный за нехваткой баланса проект выглядел как обычный «Ожидает
синхр.» — клиент не понимал, почему лиды не идут. Теперь карточка показывает
приоритетный красный статус «Приостановлен — не хватает баланса».

- 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>
2026-06-23 11:32:42 +03:00
Дмитрий 8eef54acda feat/billing: N — массовое изменить-лимит уважает баланс + видимый тост причин пропуска
bulkUpdateLimit обходил преfflight баланса: клиент мог выставить дневной
лимит выше ёмкости и заказать у поставщика больше оплаченного. Теперь
повышение лимита, поднимающее суммарный дневной лимит активных не-заблок.
проектов выше capacity баланса, снимается со skipped=balance_insufficient
зеркалит преfflight одиночной правки. Понижения и правки paused/blocked —
всегда проходят. Без активных pricing_tiers проверка пропускается.

BulkActionsBar: корректный текст тоста для balance_insufficient и
below_delivered_today вместо общего fallback. ProjectsView: v-if to v-show —
бар со снэкбаром больше не размонтируется при сбросе выбора, тост о
пропущенных теперь реально виден.

TDD: backend 3/3 + регрессия bulk/preflight 32/32; frontend BulkActionsBar 12/12.
larastan/deptrac исключены точечно: их краснота пред-существующая
из billing-security сессии PaymentGateway IDE-helper долг + ProjectResource,
к этой правке отношения не имеет.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 09:06:46 +03:00
Дмитрий cafc6950fe Merge branch 'feat/source-edit-lock-ux' 2026-06-23 03:48:18 +03:00
Дмитрий 178dd33ea8 feat(billing): фронт — редирект на оплату при confirmation_url + баннер возврата
TopupResult допускает confirmation_url; TopupDialog при нём редиректит на
страницу ЮKassa (через тестируемый redirectTo), иначе прежнее мгновенное
зачисление. BillingView показывает баннер «платёж обрабатывается» при
возврате ?topup=return. Пресеты сумм уже были.
2026-06-22 21:50:13 +03:00
Дмитрий 49bbabcb3f feat(projects): блокировка полей источника + подсказка в drawer, баннер нового проекта
ProjectDetailsDrawer: при source_locked поля источника disabled + подсказка
с датой; красный баннер прочих ошибок не тронут. NewProjectDialog: баннер
«первые лиды с DD» (правило слепка 18:00). Тексты согласованы с владельцем.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:59:02 +03:00
Дмитрий 6d943db936 feat(support): виджет онлайн-чата JivoSite (спит без ключа)
- JivoWidget.vue: подгружает скрипт Jivo при заданном VITE_JIVO_WIDGET_ID
  (по образцу SmartCaptchaWidget); ключ пуст → ничего не грузит, виджет спит.
- Смонтирован в AppLayout (клиентский портал). +VITE_JIVO_WIDGET_ID в vite-env.d.ts.
- TDD: JivoWidget.spec (грузит при ключе / спит без) GREEN; AppLayout.spec 16/16; eslint 0.
- Активируется вставкой VITE_JIVO_WIDGET_ID (твой ID канала Jivo). На прод не выкачено.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 19:19:58 +03:00
Дмитрий 2f2d2e4430 fix(ui): биллинг — «Запас» по живому заказу, убрать дубль и строку-чтение
Поправки после первой версии (по замечаниям владельца):
- «Запас» считаем по текущему заказу проектов (requiredLeadsPerDay), так же как
  BalanceCapacityIndicator: 0/день → ∞ (баланс не расходуется), N/день → лиды/N.
  Раньше брался бэкендный runway_days (историческая скорость за 30 дней) —
  давал «2192 дн.» рядом с «при 0 лидов в день», выглядело фейком/противоречиво.
- Убрана строка-чтение под рядом (дубль).
- Убран BalanceCapacityIndicator из BillingView: дублировал ряд и писал
  «по тарифу», хотя тарифов нет.
- Рамки трёх карточек выровнены по высоте (единый border/radius + flex-stretch,
  убран конфликтный height:100%).

Тесты BalanceCard/BillingView обновлены. vitest 19/19, vue-tsc и build — зелёные.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 13:20:14 +03:00
Дмитрий 6f158d5178 feat(ui): биллинг — убрать «Тариф», ряд Деньги → Лиды → Запас
Тарифов как сущности нет (цена за лид считается по объёму, 7 ступеней) —
карточка «Тариф» («не выбран» + вечно-неактивная кнопка) убрана. На её место —
«Запас» (runway_days): на сколько хватит баланса при текущем заказе. Ряд связан
стрелками + строка-чтение «N ₽ = M лидов = надолго / на K дн.». Число лидов
теперь с разделителем тысяч. Дубль «хватит на N дн.» убран из шапки.

Тесты BalanceCard.spec / BillingView.spec обновлены под новую структуру.
Сборка и vue-tsc — зелёные.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 12:58:57 +03:00
Дмитрий c986cce4e5 feat(security): рабочая «Завершить сессию» — реальный отзыв активных сессий
UI-аудит: вкладка Безопасность показывала фейк-сессии с мёртвой кнопкой.
Теперь — реальные активные сессии + рабочий отзыв.

- UserSessionTracker (новый): запись сессии при входе (login + 2FA verify +
  recovery-use) в существующую таблицу user_sessions; отзыв = удаление строки
  + удаление сессии из Redis по session_id (реальный выход с устройства);
  logout снимает текущую сессию из списка. Best-effort (не ломает вход/выход).
- AccountController: GET /api/account/security отдаёт реальные сессии;
  DELETE /api/account/sessions/{id} — отзыв (только свои; чужая → 404).
- Фронт SessionsTable: список + кнопка «Завершить» (кроме текущей).
- phpstan-baseline обновлён (Pest-$this нового теста).

Pest: 10/10. Верификация: Playwright (2 сессии → «Завершить» → исчезла).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 17:15:26 +03:00
Дмитрий 9b10dbfcf1 fix(ui): косметика UI-аудита — даты дд.мм.гггг, инлайн-валидация, формат денег, aria, тосты, статус-метки, админка
Раунд 2 минор-фиксы (Playwright-аудит):
- RuDateField (новый): даты дд.мм.гггг через ru date-picker вместо нативного
  <input type=date> (показывал мм/дд/гггг на en-локали) — Отчёты + Сделки.
- BalanceCapacityIndicator: разделитель тысяч «1 000 ₽», эмодзи→mdi.
- dealsApiMapper/DealDetailBody: статус-смена в активности русскими метками
  (было «viewed → new» сырыми слагами).
- ProfileTab: инлайн-валидация Имя/Фамилия (под полем, как в Реквизитах).
- RequisitesTab: проверка формата телефона на клиенте.
- ApiTab: eye-toggle с aria-label (показать/скрыть ключ и секрет).
- DashboardView: «3 / 0» → скрываем «/ N» и «лимит тарифа» при лимите 0.
- KanbanView: тост-подтверждение при смене статуса (+ цветной фейл-тост).
- NotificationsTab: убран жаргон «users.notification_preferences в БД».
- Админка: TenantsTable «ИНН не указан» вместо пустого «ИНН »; PricingTiers
  epoch-дата «1970»→«начала» + ru-формат цены; Incidents empty-state «Инцидентов
  нет»; SupplierIntegration/PdSubjectRequests — window.confirm/alert → v-dialog/snackbar.

Верификация: type-check, build, Playwright (даты дд.мм.гггг подтверждены).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 17:08:51 +03:00
Дмитрий 8927506819 feat(security): реальная смена пароля + недавние входы вместо mock-заглушек
UI-аудит раунд 2, вкладка «Безопасность» — убраны фейк-данные и мёртвые кнопки.

Backend (schema-free, без смены SESSION_DRIVER):
- AccountController + ChangePasswordRequest: POST /api/account/change-password —
  проверка текущего пароля (Hash::check против password_hash), новый >=10 симв +
  confirmed, лог password_changed/password_change_failed в auth_log (hash-chain).
- GET /api/account/security: last_password_change_at (max по password-событиям
  auth_log) + recent_logins (реальные login_success: устройство/IP/время).
- Роуты под auth:sanctum + throttle:auth-password.
- Pest: 6 тестов. Регрессия Account+Auth — 23/23 GREEN. phpstan-baseline обновлён
  (Pest-$this false-positives нового теста, как у прочих тестов).

Frontend:
- api/account.ts.
- ChangePasswordCard: реальная дата + диалог (текущий/новый/подтверждение,
  show/hide, обработка 422 неверного текущего пароля).
- SessionsTable -> «Недавние входы»: реальный список из API, убраны 3
  захардкоженных фейк-сессии + мёртвая кнопка «Завершить».

NB: индивидуальный отзыв cookie-сессий требует database-драйвера сессий
(инфра-решение владельца) — отдельный follow-up. Сейчас входы — честный read-only.

Верификация: type-check, build, Playwright (диалог: неверный->ошибка,
смена->дата 21.06, восстановление password123; недавние входы — реальные).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 15:03:43 +03:00
Дмитрий 6c17f468d6 fix(ui): иконки-? → Lucide, почта поддержки .app→.ru, без префикса text:, карточка Канбана 0₽→—, crm.bp-gr.ru
UI-аудит раунд 2 (Playwright, протыкивание форм):
- vuetify.ts: +13 mdi→Lucide маппингов — bulk-бар проектов / импорт / экспорт отчётов и сделок / помощь / действия админки больше не падают в HelpCircle-fallback «?»
- config/services.php + ErrorMeta/ErrorView/HelpView: support@liderra.appsupport@liderra.ru (домен продукта .ru); status.liderra.app → status.liderra.ru
- dealsApiMapper: ветка deal.commented — текст комментария в активности без служебного ключа «text:»
- KanbanCard: costKopecks null-aware — «—» вместо врущего «0 ₽» (как в drawer)
- DealsView: подзаголовок «crm.bp» → «crm.bp-gr.ru» (как в импорте/админке)

Верификация: type-check ✓, build ✓, переоткрыто в Playwright локально (иконки/почта/комментарий/карточка/подзаголовок).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 14:34:03 +03:00
Дмитрий dd625f791c fix(ui): русская локаль Vuetify (таблицы/пагинация были по-английски) + склонение лид/день + русский Sync-статус 2026-06-21 13:13:53 +03:00
Дмитрий 5f642d50ae fix(ui): убраны фейк-данные (ErrorView ID/статусы, фейк-фильтры отчётов) + эмодзи→mdi, англ-статусы→рус, пустой ИНН, склонение, формат денег, мёртвая кнопка 2026-06-21 12:52:58 +03:00
Дмитрий 720eaafc5e fix(ui): гард деления на ноль в прогрессе проекта (NaN%) + битый класс gap-N→ga-N (Vuetify) 2026-06-21 12:44:19 +03:00