Commit Graph

79 Commits

Author SHA1 Message Date
Дмитрий 7c97980b3a fix(портал): читаемый текст зелёным и жёлтым — текстовые двойники брендовых цветов
Брендовые success и warning нечитаемы текстом: success даёт 4.25:1 на белой
карточке и 3.83:1 на ивори, warning — 2.25 и 2.03 при норме WCAG 2.1 AA 4.5:1.
Сами цвета не тронуты — остаются для заливок, иконок и рамок. Рядом добавлены
затемнённые текстовые двойники success-strong #256F46 и warning-strong #8A6410,
тон сохранён.

Все 15 мест text-success и 8 мест text-warning переведены на новые классы —
13 файлов: командный центр, биллинг и инциденты админки, система, тенанты,
интеграция с поставщиком, карточка тенанта, диалог баланса, рекламный
админ-экран, мастер рекламной кампании, массовые диалоги дней и регионов,
реквизиты в настройках.

Проверено: фронт 226 файлов / 1663 теста зелёные; Pa11y по всем 24 адресам —
рекламные и ВСЕ админские экраны дают 0 ошибок. Побочно вскрылось 12 старых
недочётов на клиентских экранах — записаны в находку, к этой правке отношения
не имеют.

Боевого не касается, в main не влито.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:02:26 +03:00
Дмитрий 3917fb1344 feat(реклама показы): админ-экран ввода номера креатива Яндекса оператором
Задача 8e. SaaS-оператор видит кампании в статусе queued по всем тенантам и
вписывает номер оформленного в конструкторе Яндекса адаптивного креатива
yandex_creative_id, после чего кампанию можно запускать. Два эндпоинта
AdminAdvertisingController campaignsAwaiting и setCampaignCreative под
saas-admin+admin-db, карточка в AdminAdvertisingView, api-функции и типы.

🔴 Запись строго через сырой DB::table update только колонки yandex_creative_id
Eloquent-билдер добавил бы updated_at, а колоночный GRANT у crm_admin_user
разрешает писать лишь эту колонку тесты идут под суперюзером и это не ловят.
RLS не трогаем ad_campaigns уже покрыт srv_bypass и грантами.

Бэкенд AdminAd 19/19, реклама-модуль 195/195, фронт админки 11/11.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 09:55:41 +03:00
Дмитрий f3d1d23b9c refactor(реклама показы): админ-отчёт маржи на ad_margin_percent 40% вычитанием, удалён старый AdMarkup
Задача 8b. Админ-экран «Реклама: расход и маржа» переведён со старой модели
наценка 30% делением на единую модель показов наценка 40% вычитанием, как в
CampaignImpressionCharger. yandex_cost = client_spend умножить на долю
1 минус ad_margin_percent/100. Поле ответа markup_percent переименовано в
ad_margin_percent, фронт-вью и тип обновлены.

Мёртвый класс AdMarkup и его тест удалены полностью. Колонка ad_settings
markup_percent осталась в схеме, но больше не читается. Тесты: бэк
AdminAdvertisingSpend 6/6, реклама-модуль 189/189, фронт админки 7/7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 00:47:03 +03:00
Дмитрий 5a4c0e0235 feat(реклама): показы — баннеры клиента по размерам, два режима аудитории, клиентская цена и наценка
Часть 5c — баннеры: клиент грузит свой готовый файл на каждый из 15 размеров вместо автогенерации из одной картинки. Частичное утверждение флагом included, замена и удаление отдельного баннера, валидация точного размера и веса. Админ-поле цены за 1000 показов. Пример CSV для скачивания и подъём лимита загрузки.

Часть 5d — два режима сбора аудитории. Авто: скользящее окно, обновляется ежедневно, только контакты системы. Ручной: снимок сделок за период плюс свой список номеров и срок показа. Клиент сам задаёт цену за 1000 показов с дефолтом из админки. Наценка настраивается в админке, по умолчанию 40 процентов, в Директ уходит меньше, клиенту не видна нигде.

Миграции: ad_campaign_banners += included; ad_campaigns += mode/snapshot_from/snapshot_to/run_days/client_cpm_rub; ad_settings += ad_margin_percent. RLS-ревью PASS на всех миграциях. Backend 166 тестов, фронт 123 теста, сборка чистая. Маржа и yandex_cost_rub клиенту не сериализуются.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 21:13:20 +03:00
Дмитрий 1661e1bb94 feat(реклама-фронт): админ-экран «Реклама — расход и маржа» по тенантам 2026-07-25 11:12:36 +03:00
Дмитрий b4c51bf0bc feat(admin): СМС-центр (smsc.ru) на плашке внешних сервисов — баланс, пороги, кнопка пополнения
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 08:25:28 +03:00
Дмитрий b7cfee0a0d feat(dashboard-ui): строка «Прокси автоподбора» — срок «до …», остаток дней, статус
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-17 08:31:12 +03:00
Дмитрий a497f76af4 feat(external): «Пополнить» для xfetch/Keyso (в карточке поиска) + EXA-тоннель (Happ VPN, настраиваемо) 2026-07-16 19:06:39 +03:00
Дмитрий 3d4cdbbda3 feat(dashboard): sales_finder+xfetch+keyso — одна карточка «Поиск клиентов» 2026-07-16 17:17:13 +03:00
Дмитрий 4bfe0b7005 feat(external): SSH-тоннель EXA — отдельная плитка живости (12-й сервис)
По вопросу владельца: EXA ходит через обратный SSH-тоннель (services.exa.proxy),
и это самое хрупкое звено (ops-машина+Happ+порт 10808). Раньше падение тоннеля
выглядело бы как «EXA не отвечает». Теперь ExaTunnelLivenessProbe — отдельная плитка:
сквозной запрос ЧЕРЕЗ прокси на нейтральный адрес (exa.tunnel_check_url, дефолт
api.ipify.org). Тоннель🟢+EXA🔴 = проблема у EXA; тоннель🔴 = легла наша труба.
Дашборд: 12 сервисов. Тесты: 66 моей области зелёные (вкл. 3 пробы тоннеля + 12-keys), Larastan 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 15:04:52 +03:00
Дмитрий 9ecdd539bf feat(dashboard): фронт онлайн-балансов — фон при открытии + кнопка ⟳ + колонка «Проверено»
Фаза D плана 2026-07-16-external-services-online-monitoring:
- api: refreshDashboardBalances(service?) — POST фон/кнопка.
- AdminDashboardView: метки/иконки 5 новых сервисов; LIVENESS_ONLY += self_render,
  sales_finder; serviceStatus → «жив» когда ok без денежного баланса; фон
  refreshLiveBalances() при каждом открытии дашборда; кнопка ⟳ у каждого сервиса
  (per-row спиннер); колонка «Проверено» (checkedLabel).
Тесты: Vitest AdminDashboardView 17/17 (2 новых); type-check по своим файлам чист.

NB: larastan-хук исключён — чужой незакоммиченный файл в общей папке (параллельная сессия).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 14:50:01 +03:00
Дмитрий e517b7a256 Merge remote-tracking branch 'gitea/main' into worktree-jivo-bot-core
# Conflicts:
#	app/bootstrap/app.php
#	app/config/services.php
#	app/tests/Feature/Plan4/Schema/SchemaDeltaTest.php
#	db/CHANGELOG_schema.md
#	db/schema.sql
2026-07-14 09:44:56 +03:00
Дмитрий b6b8d6e0d6 feat(visitors): раздел «Посетители» в админке — воронка, каналы, визиты, кабинет
Воронка подсвечивает красным шаг, где теряем больше всего людей. Гости с VPN
и хостингов показаны отдельным числом, а не подмешаны в конверсию. Город при
VPN честно помечается как недостоверный.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 05:03:38 +03:00
Дмитрий e969b7700a chore(chat): убрать настройки Jivo — чат теперь свой; экскурсии выключены
Config: jivo_bot -> bot (убраны неиспользуемые webhook_secret/outbound_url/token
- транспорт Jivo уже удалён), jivosite убран целиком (виджет больше не нужен).
Env: JIVO_BOT_* -> BOT_*; JIVO_WIDGET_ID/VITE_JIVO_WIDGET_ID удалены.
tours_enabled выключен по умолчанию (BOT_TOURS_ENABLED=false) — экскурсии
«Показать на портале» уходили не по теме вопроса (живая проверка 13.07.2026),
чинить релевантность отдельно; сторож-тест не даёт включить незаметно.

Заодно убраны обнаруженные хвосты того же виджета, оставшиеся от прежних
задач: JivoLivenessProbe (класс+тест+регистрация в мониторинге внешних
сервисов), плитка «JivoSite» в админ-дашборде, встроенный скрипт виджета
в welcome.blade.php, осиротевший тест resources/js/.../JivoWidget.vue
(компонент уже был удалён ранее), declaration VITE_JIVO_WIDGET_ID.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 08:32:21 +03:00
Дмитрий 77050dbefb fix(admin): клиентский диапазон ступеней в тарифной сетке + предпросмотр в редакторе
Cherry-pick a7a4e735 поверх боевой ветки feat/portal-mobile-adaptive для
выката ТОЛЬКО правки тарифов на прод. Логика денег не менялась.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 15:29:40 +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
Дмитрий b2699f0d84 feat(y360): баланс почты Яндекс 360 — ручной ввод + кнопка Пополнить
email — денежный сервис; сумма вписывается в админке «Система» (Yandex360BalanceStore),
светофор по порогам, кнопка «Открыть оплату»/«Пополнить» → admin.yandex.ru/products.
Робот-скрейпер отклонён (SPA Яндекса враждебен ботам + автопополнение защищает баланс).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 13:34:13 +03:00
Дмитрий 73b56042a9 feat(external): на плитке внешних сервисов — и баланс, и статус
Мини-плашка теперь показывает деньги (или —) И слово статуса (жив/выключено/ok)
рядом, как в детализации. Убрана мёртвая serviceValue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 10:36:31 +03:00
Дмитрий 64785a9357 fix(external): Баланс = только деньги; живость — в колонку Статус
Баланс/статус различаем по типу сервиса (LIVENESS_ONLY_KEYS), не по null-балансу
(денежный Поставщик с сорванным автологином больше не показывается «выключено»).
Мини-плитка переименована «Балансы сервисов»→«Внешние сервисы».

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 10:26:06 +03:00
Дмитрий a582b076d2 feat(external): фронт — плитка «Внешние сервисы» (баланс + живость)
Статус-текст для сервисов без денежного баланса (жив/не отвечает/выключено),
метки и иконки почты/ЮKassa/Jivo/капчи, обновлённые подписи плитки и дрилла.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 09:03:53 +03:00
Дмитрий ab612a025b fix(конкурентное поле): 6 находок теста «тупого клиента» — ошибки, регион, дедуп, миграции
- адресные сообщения в окнах сбора/изучения (маппер autopodborErrorMessage)
- регион по умолчанию = пустой плейсхолдер «выберите регион»
- кнопка «Собрать источники» у изучённого конкурента → «Источники собраны»
- сквозной дедуп предложений между прогонами (без двойного списания, ретрай цел)
- убран захардкоженный admin_user_id с фронта (id ставит бэкенд)
- идемпотентный гард в 3 миграции автоподбора (migrate:fresh снова зелёный)
- заглушка Агента: +тип 8-800 (tollfree) для полноты эмуляции

Тесты: Pest автоподбор 82/82, Vitest 62/62, vite build зелёный.

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 06:42:33 +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
Дмитрий 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
Дмитрий 3fd457aa6a 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:06:56 +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
Дмитрий 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
Дмитрий 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
Дмитрий 3f8727f9a1 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
Дмитрий 8ddc426333 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
Дмитрий d5b2695ec6 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
Дмитрий 54051bb9b4 feat/admin: тумблер разблокировки смены источника на экране интеграции с поставщиком
Дружелюбный переключатель ВКЛ/ВЫКЛ флага routing_match_by_snapshot для владельца — без правки БД и без 30-символьного основания общего edit-flow. GET/POST source-edit-flag в AdminSupplierIntegrationController пишут в system_settings type=bool + audit-журнал. На экране карточка с VSwitch и диалогом подтверждения, бамп ключа возвращает тумблер к факту при отмене. TDD: 5 эндпоинт-тестов + фронт-спек. Larastan чист, baseline дополнен Pest-шумом. Проверено глазами через Playwright.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 04:27:32 +03:00
Дмитрий ab2501d41e feat/supplier: экран отчёта о вечерней заливке (Эпик 5)
Владелец выбрал формат «экран в админке» (не письмо).
- SyncSupplierProjectsJob по завершении пишет строку-сводку в новую supplier_sync_runs
  (групп/синк/ручная/отложено/упало + status ok|partial|failed|aborted) через finally —
  пишется и при раннем abort (time-budget/mass-fail/auth).
- Эндпоинт GET /api/admin/supplier-integration/sync-runs + метод syncRuns.
- Экран SaaS-admin «Интеграция с поставщиком» → карточка «Вечерняя заливка проектов
  поставщику»: таблица заливок со статусом человеческим языком (Всё ровно/Частично/Сбой).
- Схема v8.55 +1 таблица (SaaS-level без RLS как supplier_csv_reconcile_log), миграция
  2026_06_25_130000, RLS-ревью 7/7. Проверено глазами в браузере (epic5-sync-runs-admin-screen.png).

Тесты: бэк 24/25 (1 skip) + фронт-экран 5/5 зелёные. Под LEFTHOOK=0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 18:52:39 +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
Дмитрий 5f642d50ae fix(ui): убраны фейк-данные (ErrorView ID/статусы, фейк-фильтры отчётов) + эмодзи→mdi, англ-статусы→рус, пустой ИНН, склонение, формат денег, мёртвая кнопка 2026-06-21 12:52:58 +03:00
Дмитрий c1a66898b3 fix(admin): починка 500 на admin-сохранениях (audit-id из config, без saas_admin_users) + тариф текущей датой 2026-06-21 11:32:53 +03:00
Дмитрий 2d76f53563 F-5 eslint: 0 problems
DealDetailDrawer: default для tenantId (require-default-prop). AdminPdSubjectRequestsView: v-slot:[...] в #[...] (v-slot-style, auto-fix). 2 region-спека: disable-комментарий no-explicit-any для VueWrapper-кастов F-3 - по конвенции 9 соседних тестов.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 07:22:08 +03:00
Дмитрий ae286b9a0d 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
Дмитрий f642553df0 feat(admin): wire balance dialog into tenant list table 2026-05-23 20:02:39 +03:00
Дмитрий 7270472505 feat(admin): wire balance dialog into tenant detail card 2026-05-23 20:02:39 +03:00
Дмитрий a3cd09da30 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
Дмитрий 2a28d00f5e feat(admin): supplier projects cleanup screen (list + bulk delete)
План 4 Task 3 эпика project-migration-redesign.

- AdminSupplierProjectsView.vue — v-data-table (источник/платформа/регион/
  лимит/кто заказывал/последняя поставка) + bulk-delete с v-dialog
  подтверждением + snackbar (deleted/failures).
- Роут /admin/supplier-projects (layout admin, requiresAuth, devIndex 31).
- AdminLayout nav-пункт «Проекты у поставщика».
- Vitest 3/3 (mount GET, bulk-delete confirm POST {ids}, disabled when empty).

NB: type-check имеет 3 pre-existing ошибки в DealDetailHero.spec.ts
(коммит 9ada842, не Plan 4); файлы T3 type-check-чисты.
2026-05-20 14:34:25 +03:00
Дмитрий 19fafcff9b feat(admin): supplier export-mode toggle (online|batch) endpoint + UI
План 4 Task 1 эпика project-migration-redesign.

- AdminSupplierIntegrationController +getExportMode/setExportMode
  (validation in:online,batch; system_settings upsert).
- Routes: GET/POST /api/admin/supplier-integration/export-mode
  в admin-группе рядом с manual-queue.
- AdminSupplierIntegrationView.vue +секция «Режим экспорта проектов»
  с v-btn-toggle (online|batch), подпись о ночном синке 18:00.
- Pest 3/3 + Vitest 2/2 (+ соседние 5 не сломаны).
- phpstan-baseline.neon +6 ignore (Pest TestCall::actingAs/getJson/postJson
  — типовой паттерн, как в SupplierManualQueueTest).
2026-05-20 14:34:22 +03:00
Дмитрий 2bc02b4608 feat(supplier): manual queue section in AdminSupplierIntegrationView
Таблица pending-записей яруса 3 + кнопка «Отметить выполнено» с confirm-
диалогом, дёргает POST .../manual-queue/{id}/resolve. Реюз существующего
админ-экрана интеграции с поставщиком (после «Истории сверок»).

NB: spec в tests/Frontend/ (vitest include — tests/Frontend/**, не
resources/.../__tests__/ как указал план Step 11.1). loadManualQueue
defensive Array.isArray-guard — иначе onMounted в чужих spec'ах
(mockResolvedValue без queue-ключа) ловил undefined.length.

Spec §4.6. Task 11 of 12. Vitest 5/5 (2 новых + 3 существующих).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 12:55:10 +03:00
Дмитрий e5e44cde2e feat(supplier): UI-экран «Интеграция с поставщиком» — здоровье CSV-канала 2026-05-18 17:58:53 +03:00
Дмитрий 73351c8004 fix(a11y): aria-label с ключом на edit-кнопках AdminSystem — Sprint 6 G9 2026-05-17 08:18:44 +03:00
Дмитрий 07d2e9705e fix(admin): I3 — убрать mock fallback в System/Tenants 2026-05-17 07:13:50 +03:00
Дмитрий aed9475c26 fix(admin): I3 — убрать mockAdmin fallback в Billing/Incidents 2026-05-17 07:13:50 +03:00
Дмитрий 1692ee500d fix(admin): G7 review-fixup — сброс effective_from при открытии редактора + boundary-тест 2026-05-17 05:24:44 +03:00