Commit Graph

79 Commits

Author SHA1 Message Date
Дмитрий 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
Дмитрий 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
Дмитрий 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
Дмитрий 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
Дмитрий 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
Дмитрий 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
Дмитрий 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
Дмитрий 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
Дмитрий 02a8a90e4d feat дашборд: Этап 2 — живые плитки Лиды и Заказ у поставщика
Backend: AdminDashboardController +leads/+supply эндпоинты, summary дополнен
плитками leads/supply; сверка заказа вынесена в чистый сервис
SupplyReconciliation (спрос → формула computeOrder=max(max,⌈Σ/3⌉) → факт →
рассинхрон). Лиды: доставлено сегодня / зависшие 4ч+ / нераспределённые /
% доставки — cross-tenant под pgsql_admin.

Frontend: плитки Лиды и Заказ оживлены (убраны заглушки «Этап 2»), drill
с KPI и таблицей групп спрос→формула→факт→совпадает.

Тесты: SupplyReconciliation unit 3/3, Leads/Supply/Summary feature,
admin-срез 87 зелёных, фронт 10/10. stan 0, pint/eslint/type-check/build чисто.
phpstan-baseline перегенерирован (getJson false-positive на новых тестах).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 14:32:31 +03:00
Дмитрий 67ea5d32b4 feat дашборд-fe: экран Командного центра + API-клиент + роут /admin/dashboard
Этап 1 фронтенда дашборда «Командный центр»: плитки Финансы и Здоровье
с живыми данными, заглушки Лиды и Заказ у поставщика на Этап 2,
drill-детали, клик по клиенту ведёт в карточку тенанта.
Редирект /admin теперь на /admin/dashboard.

Тесты: AdminDashboardView 8/8, router.spec обновлён под новый редирект.
type-check / vite build / eslint — чисто.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 13:43:10 +03:00
Дмитрий 7ac9af7c79 feat: убрать лимит по числу проектов — ограничение только по балансу/лидам
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
Правило продукта: ограничений по количеству проектов нет, лимит только
по балансу и заказанным лидам. Убран гейт tenants.limits.max_projects
в ProjectService::create и показ лимита проектов на дашборде. Поле limits
оставлено как резерв; max_users и api_rps в коде не используются.

Заодно фикс типа в EditProjectDialog.spec: sampleProject типизирован
настоящим Project, source_locked больше не краснит vue-tsc.

Тесты: ProjectsStore 13/13, DashboardSummary 11/11, DashboardView 8/8,
EditProjectDialog 7/7; vue-tsc чисто; pint чисто; vite build ок.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 12:47:49 +03:00
Дмитрий 63a2d53255 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
Дмитрий 0bcafe7ad6 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
Дмитрий 146174a2e2 feat: публичные юр-страницы оферта/политика/возврат + реквизиты + страница цен под модерацию ЮKassa
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
2026-06-24 09:31:52 +03:00
Дмитрий 3daa4995ea feat(billing): фронт — редирект на оплату при confirmation_url + баннер возврата
TopupResult допускает confirmation_url; TopupDialog при нём редиректит на
страницу ЮKassa (через тестируемый redirectTo), иначе прежнее мгновенное
зачисление. BillingView показывает баннер «платёж обрабатывается» при
возврате ?topup=return. Пресеты сумм уже были.
2026-06-22 21:50:13 +03:00
Дмитрий 2225a8487e feat(security): рабочая «Завершить сессию» — реальный отзыв активных сессий
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
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
Дмитрий ec6434ee9a feat(security): реальная смена пароля + недавние входы вместо mock-заглушек
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
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
Дмитрий 54d0bf8fe7 chore(frontend): убрать мёртвый тип 'reminder' и устаревший докблок
F-REMIND: фича «Напоминания» снята (G3, drop_reminders_table), но во фронте
остались следы. Убран осиротевший член типа NotificationEventKey 'reminder'
(api/auth.ts) — он нигде не рендерился (в EVENTS NotificationsTab его нет,
мёртвого переключателя не было) — и устаревшее упоминание «напоминания» в
докблоке DealDetailBody.vue. type-check + eslint чисты.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 04:46:08 +03:00
Дмитрий 173b089629 feat(G7-B): клиентская плашка impersonation + редирект/ключ в диалоге + leave 2026-06-19 17:09:59 +03:00
Дмитрий b133ceb98a feat(G7-A): экран «Помощь» (форма-заявка) + пункт меню + роут
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 15:04:17 +03:00
Дмитрий 4c3e57bf9b feat(G3): убрать преференцию «Напоминание» + хвосты фронт-тестов
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:29:46 +03:00
Дмитрий cbf8b4fb43 feat(G3): убрать next_reminder_at из фронт-слоя сделок
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:29:35 +03:00
Дмитрий a49b201d33 feat(G3): убрать экран «Напоминания» и раздел в карточке сделки
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:18:49 +03:00
Дмитрий 2eb2f3d076 feat(G1/SP3b): api-обёртка реквизитов (get/update/lookup-inn)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 07:24:26 +03:00
Дмитрий bacc7c5e24 feat: G1/SP3a фронт входа — регистрация + подтверждение почты
Переработка register под новый бэкенд SP1 (код на почту), новый ConfirmEmailView, капча-шов, роут /confirm-email. Проверено Playwright: register→код→confirm→dashboard, негатив, fallback email. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 23:33:26 +03:00
Дмитрий 1cb3b56f70 fix(dashboard): верхняя строка дашборда — настоящие числа вместо заглушки — F5
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
Строка-приветствие показывала захардкоженную рыбу: +3 новых лида с утра,
сегодня 11 / вчера 38, средняя стоимость 2 248 руб. Числа ни к чему не были
привязаны — остаток прототипа Sprint 4.

Бэкенд: DashboardController.summary отдаёт avg_lead_cost_rub — среднее
фактически списанных rub-сумм за окно периода: AVG price_per_lead_kopecks
WHERE charge_source rub делить на 100; null если в окне нет rub-списаний.
Тот же источник, что карточка сделки F2.

Фронт: DashboardPageHead принимает пропы сегодня/вчера/средняя; сегодня и
вчера берутся из activity.points последняя точка сегодня; средняя из
avg_lead_cost_rub, прочерк при null. Размытое +3 с утра убрано.

TDD: 2 Pest DashboardSummaryTest 10/10 + 4 vitest DashboardPageHead;
полная фронт-сюита 959 passed / 3 skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:02:04 +03:00
Дмитрий 4d8a1af099 feat(deals): карточка показывает реальную стоимость лида — F2
Backend: GET /api/deals/{id} отдаёт cost_kopecks — снимок rub-списания из
lead_charges по deal_id, либо null для prepaid/не списано. Frontend: ApiDeal.cost_kopecks
→ MockDeal.costKopecks → карточка DealDetailBody показывает formatCost(costKopecks/100)
либо прочерк вместо вводящего в заблуждение 0 рублей. TDD: 3 Pest + 4 vitest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 13:30:46 +03:00
Дмитрий f94552d452 WIP чекпойнт: lead-region/supplier бэкенд + фронт-редизайн + Pint + тесты
92 файла одной пачкой. Исключены чужие зоны: CLAUDE.md, .claude/settings.json, docs/observer/.pii-counters.json.
gitleaks staged: no leaks found. Не верифицировано тестами - сохранение труда в историю.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 05:17:12 +03:00
Дмитрий e1601e7862 feat(billing-v2-c): UI префлайт Task 1.10 — баннер заморозки, индикатор ёмкости, диалог перегрузки
Spec C §3.6/§6.2. Бэкенд: GET /api/billing/balance-status (frozen + capacity + required + дефицит ₽/leads), Pest 6. Фронт: BalanceFrozenBanner (в AppLayout, глобально), BalanceCapacityIndicator (в BillingView под балансом), ProjectLimitOverloadDialog (409-перехват в NewProjectDialog: save-blocked/set-zero), tenantStore + api getBalanceStatus. Vitest +18.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 20:39:21 +03:00
Дмитрий 6385e6fce6 feat(admin): TenantBalanceDialog + updateTenantBalance api client 2026-05-23 20:02:38 +03:00
Дмитрий 383b105bf5 feat(billing-v2): BalanceCard — ≈ N лидов via affordable_leads, drop (ГЦК) 2026-05-23 18:46:18 +03:00
Дмитрий 77e98afaa6 feat(pd): 152-ФЗ право на удаление — минимум (hole #4)
Закрывает дыру #4 аудита журналирования. Объём по выбору заказчика — МИНИМУМ:
 Админ-API + кнопка в админке для удаления ПДн субъекта
 Сервис анонимизации (users + supplier_leads + deals + webhook_log)
 Журнал факта удаления в pd_processing_log
 БЕЗ формы самообслуживания на стороне субъекта
 БЕЗ email-подтверждения
 БЕЗ 30-дневного SLA (trigger deadline_at уже в схеме)

Что добавлено:
* Eloquent-модель `App\Models\PdSubjectRequest` (таблица уже была в схеме)
* Сервис `App\Services\Pd\PdErasureService::eraseSubject()`:
  - cross-tenant через pgsql_supplier (BYPASSRLS)
  - транзакционно (rollback при ошибке)
  - users: email→erased-{id}@deleted.local, first_name→Удалено, last_name→null,
    phone→+7000{id}
  - supplier_leads: phone→+7000XXXXXXX, raw_payload→{erased:true}
  - deals: phone→+7000XXXXXXX, contact_name→Удалено (только если есть phone)
  - webhook_log: batched UPDATE по 500, raw_payload→{erased,erased_at}
  - pd_processing_log запись action=deleted за каждого user/lead с
    actor_admin_user_id (hash-chain audit_chain_hash триггером сам подписывает)
  - При requestId — pd_subject_requests SET status=completed, completed_at,
    response_text счёт
* Контроллер `AdminPdSubjectRequestsController`: index/show/store/executeErasure
* Маршруты под middleware(saas-admin): GET/POST /api/admin/pd-subject-requests,
  GET /{id}, POST /{id}/erase
* Vue: `AdminPdSubjectRequestsView` (Quiet Luxury, таблица + диалог создания +
  кнопка Анонимизировать для request_type=deletion); ESLint требует
  v-slot:[`item.X`]= вместо #item.X для динамических slot-имён с точкой
* Пункт меню в AdminLayout.vue + route /admin/pd-subject-requests

NB: реальная схема — users.first_name/last_name/phone/email; supplier_leads
имеет только phone (нет contact_*); deals имеет phone+contact_name (нет
contact_email); webhook_log JSONB. PdErasureService адаптирован под факт.

Тесты: 12/12 passed (63 assertions, ~2.6s) — index pagination, store +
deadline trigger (+30 дней), eraseSubject анонимизация user/lead/deal/log,
pd_processing_log запись, request status→completed, отклонение
не-deletion типов, gate saas-admin, InvalidArgumentException.

Plan: docs/superpowers/plans/2026-05-23-7-holes-overview.md (#4).
2026-05-23 12:21:21 +03:00
Дмитрий c7fd90c08d fix(deals): читать проекты из конверта { data } + чинить фикстуры LeadStatus
DealsView крашился (Cannot read properties of undefined reading 'map'): listProjects() читал data.projects, но ProjectController::index() отдаёт { data: [...] } после миграции на JsonResource — availableProjects=undefined ломал .map, фильтр «Проект» был пуст. Фикс: читать data.data ?? []. + deals-api.spec.ts тест на новый конверт + защитный []. + DealDetailHero.spec.ts: фикстуры LeadStatus (isSystem/sortOrder вместо order) — устранён pre-existing type-check error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:20:53 +03:00
Дмитрий 789e7dcdb6 feat(deals/drawer): убрать «Менеджер», добавить «Тип» + «Источник» read-only
UX-request 18.05.2026 (пп.4/6/7):
- удалена секция «Менеджер»/«Не назначен» (менеджеров в системе пока нет)
- добавлен параметр «Тип» (Сайт/Звонок/СМС) — project.signal_type
- добавлен параметр «Источник» (read-only):
  - site/call → project.signal_identifier (домен или телефон)
  - sms → sms_senders[0] + ' (KEYWORD)' если sms_keyword не пустой
- удалён hardcoded «Я.Директ → landing-1»

Backend: DealController index + show + update payload расширены 4 полями
project_signal_type/identifier/sms_keyword/sms_senders + eager-load
project relation расширен.

Редактирование источника — только в карточке проекта (Task 5 плана).

Larastan baseline bumped (DealShowTest: tenant 13→20, getJson 7→10 для 3 новых тестов).
Pest 51/51 на Deal-endpoints.
Vitest 108 files / 875 passed / 3 skipped (5 новых тестов DealDetailBody).
Build 2.30s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 15:24:57 +03:00
Дмитрий 7bcfbf6bd4 feat(deals): api/deals — ApiDeal +4 fields, date-range list params, exportDealsByRange 2026-05-18 03:42:40 +03:00
Дмитрий 9f791f9f93 feat(admin): G7 — выбор effective_from тарифной сетки через date-picker 2026-05-17 05:24:44 +03:00
Дмитрий c31e199e45 refactor(admin): G3 — pricing-tiers/suppliers вьюхи на типизированный api/admin.ts 2026-05-17 05:24:44 +03:00
Дмитрий 4b0809a82d feat(ui): B2 — счётчик «Сделки» в сайдбаре из API вместо хардкода 2026-05-17 03:14:13 +03:00
Дмитрий 7f05c4ab16 feat(import): api/imports.ts + UnknownStatusesDialog (wizard маппинга)
- api/imports.ts: типы ImportLogResource/UnknownStatus/StatusMapping,
  функции uploadImport/listImports/getImport/getUnknownStatuses/resolveUnknownStatuses
  (apiClient из ./client, стиль api/dashboard.ts)
- UnknownStatusesDialog.vue: wizard маппинга незамапленных статусов воронки
  (ТЗ §6.4/§6.6), 14 канонических slug, defineExpose(selection, save)
- Vitest 3/3 (tests/Frontend/UnknownStatusesDialog.spec.ts)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 19:58:33 +03:00
Дмитрий ca0c4d9318 feat(admin): G5/G6 frontend — incident detail view + РКН-notify 2026-05-16 14:09:53 +03:00
Дмитрий c85424968e feat(admin): G4 frontend — billing row-actions menu + dialogs 2026-05-16 14:09:53 +03:00
Дмитрий b027a3cfee feat(reports): кнопка «Скачать» → signed download URL (F2 frontend) 2026-05-16 12:45:51 +03:00
Дмитрий cadaecdaf8 feat(dashboard): DashboardView на real API /api/dashboard/summary (audit C1)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 11:41:09 +03:00
Дмитрий c2cb3af4c6 feat(billing): TopupDialog + Пополнить wiring (E1)
TopupDialog (сумма + пресеты + min 100 ₽ валидация) → POST
/api/billing/topup. Кнопки «Пополнить баланс» (шапка) и «Пополнить»
(BalanceCard) открывают диалог; при успехе — refresh кошелька +
транзакций + snackbar.

Sprint 2 Plan C, audit E1 (frontend).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 08:16:08 +03:00
Дмитрий ac2c794542 feat(billing): TransactionsTable + InvoicesTable real API (E3)
TransactionsTable — server-driven история транзакций (GET
/api/billing/transactions, табы → фильтр type). InvoicesTable —
GET /api/billing/invoices с empty-state (real-but-empty до Б-1).
billingFormatters почищен (drop status/format-функций), mockBilling
ужат до pending-баннера (E4).

Sprint 2 Plan C, audit E3 (frontend pt2).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 07:56:22 +03:00
Дмитрий cc624543e9 feat(billing): BillingView wallet + BalanceCard real API (E3)
api/billing.ts (getWallet) + BillingView тянет GET /api/billing/wallet
на mount (шапка + BalanceCard, loading/error-state). BalanceCard на
реальные props с nullable-тарифом. featureLabel для feature-слагов.

Sprint 2 Plan C, audit E3 (frontend pt1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 07:32:59 +03:00
Дмитрий 298a7fa9de feat(settings): ApiTab wired to api-key + webhook endpoints (closes D2-D5)
Audit D2/D3/D4/D5: all four ApiTab buttons were handler-less and the
fields were hardcoded. Adds api/apiKeys.ts + api/webhooks.ts modules and
rewires ApiTab: loads the api-key prefix + webhook settings on mount;
Copy -> clipboard + snackbar; Regenerate -> confirm dialog -> POST
regenerate (full key shown once); Save Webhook -> PUT webhook-settings;
Test Webhook -> POST test with the result in a snackbar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 22:27:56 +03:00
Дмитрий 075a661c62 feat(settings): ProfileTab wired to PATCH /api/auth/me (closes D1)
Audit D1: ProfileTab fields were hardcoded refs and the Save button had
no handler. Rewired to the auth store + a new api/auth updateProfile()
calling PATCH /api/auth/me. Single «Полное имя» field split into Имя +
Фамилия (matches users.first_name/last_name); decorative «Роль» field
removed (no such column). AuthUser type gains phone + timezone.

SettingsView.spec.ts updated: «Полное имя» assertion changed to check
for «Имя» and «Фамилия» (collateral fix for the intentional field split).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 21:37:40 +03:00
Дмитрий e746b3c9a4 chore(cleanup): dead code removal + DemoSeeder env-conditional + schema header drift
Closes Audit #3 P2 batch (knip dead exports/components, DemoSeeder
hygiene, schema header drift).

- Remove app/resources/js/views/admin/AdminPlaceholderView.vue
  (unreferenced placeholder view — confirmed via repo-wide grep, only
  doc references remain)
- npm uninstall concurrently (no script invoked it; --legacy-peer-deps
  for Histoire 1.0-beta.1 peerDep quirk)
- 12 unused exports → internal types (remove `export` keyword):
  - api/admin.ts: AdminTenantsStats, ApiTenantMetrics,
    ApiAdminBillingSummary, ApiAdminIncidentsSummary
  - api/notifications.ts: NotificationEvent
  - api/reports.ts: ApiReportType, ApiReportFormat, ApiReportParameters,
    ReportCounts, ReportQuota
  - composables/mockBilling.ts: TxType
  - composables/useStatusPill.ts: StatusPillSlug
  All 12 are used INSIDE their own file (response shapes), just not
  exported externally — converting to internal types satisfies knip
  without losing type-checking inside the file.
- DatabaseSeeder::run() — DemoSeeder runs only in local+testing envs
  (`migrate:fresh --seed` in dev now produces demo tenant + admin@demo.local
  + 3 projects + ~14 demo deals; prod environments skip)
- db/schema.sql header line 4: «62 базовые таблицы» → «63 базовые
  таблицы (61 regular + 2 partitioned parents: deals + supplier_lead_costs)»
  Closes schema header drift finding from Phase 3.

Verification:
- vue-tsc --noEmit: 0 errors
- ESLint on touched files: 0 errors
- Pest --parallel: 742/739/3sk/0 failed (identical to baseline, no regressions)
- 2243 assertions / 34.46s

Plan: docs/superpowers/plans/2026-05-14-audit3-deferred-fixes.md Task 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 08:28:44 +03:00