Commit Graph

2669 Commits

Author SHA1 Message Date
Дмитрий d50a3d5108 feat(автоподбор): HtmlPhoneScanner — номера из кода (tel/schema/microdata/тело/email)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:16:22 +03:00
Дмитрий bc462d25fa feat(автоподбор): PhoneType — тип номера по коду
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:16:05 +03:00
Дмитрий 1b3683c6b1 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
Дмитрий 793b20a39c 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
Дмитрий 3561028dd2 docs(Конкурентное поле): прототип, план реализации, эстафета и Playwright-сверка с Омегой
HANDOFF (состояние/решения/окружение), impl-plan (фазы+догрузки), кликабельный прототип
2026-06-29-konkurentnoe-pole-proto.html (визуал-эталон), omega-visual-check (живая сверка
рабочего места с реальными конкурентами Омеги, скрины FIELD-*/PROTO-*).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 04:20:25 +03:00
Дмитрий 4387333118 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
Дмитрий 3d4261cba1 chore(схема): NB о дрейфе счётчика RLS-политик (тело 49 vs канон 47)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 16:19:51 +03:00
Дмитрий ef815c0b8c fix(автоподбор): идемпотентность джоб при ретрае + zero-price short-circuit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 16:17:44 +03:00
Дмитрий 9b4622da85 chore(схема): canon-sync schema.sql v8.58 — 3 таблицы автоподбора + RLS
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 16:09:34 +03:00
Дмитрий 23263d18a0 test(автоподбор): сквозной smoke по всем экранам + defineExpose
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 16:00:22 +03:00
Дмитрий 5ba553a0cc feat(автоподбор): фронт — экран изменения проекта
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 15:53:36 +03:00
Дмитрий 48509572b5 feat(автоподбор): фронт — экраны создания проектов и готово
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 15:47:56 +03:00
Дмитрий 3bc4325b78 feat(автоподбор): фронт — экран источников конкурента (выбор, ручной источник)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 15:41:07 +03:00
Дмитрий 361d02a256 feat(автоподбор): фронт — экраны загрузки и списка конкурентов
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 15:33:20 +03:00
Дмитрий 33ac1a5954 feat(автоподбор): фронт — экраны форм подбора и своего конкурента
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 15:22:36 +03:00
Дмитрий 17d93a144b feat(автоподбор): эндпоинт конкурентов прогона + competitor_id в RunResource
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 15:15:16 +03:00
Дмитрий aa807c0ed4 feat(автоподбор): фронт — каркас экрана, вход, роут, пункт меню
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 15:10:29 +03:00
Дмитрий e52e958484 feat(автоподбор): фронт — api-клиент и Pinia store
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 14:58:51 +03:00
Дмитрий 8cc6511edd feat(автоподбор): ручные эндпоинты — manual-study и добавление источника
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 14:50:37 +03:00
Дмитрий 02d2163e75 feat(автоподбор): API ядро — контроллер, роуты, ресурсы
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 14:42:41 +03:00
Дмитрий 3c8886c97f feat(автоподбор): stripBadge — чистое имя конкурента без значка
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 14:33:11 +03:00
Дмитрий f208fe2f65 feat(автоподбор): создатель проектов из источников (имя+значок+суффикс)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 14:27:04 +03:00
Дмитрий 98b26f6191 feat(автоподбор): RunService — старт, гейт баланса, один in-flight
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 14:20:54 +03:00
Дмитрий d9b3e8dbe1 feat(автоподбор): джоба резолва по названию
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 14:16:14 +03:00
Дмитрий a3b68dbb95 docs(автоподбор): записка для продолжения после компакта (state + что прочитать)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 13:54:44 +03:00
Дмитрий 78d1965430 feat(автоподбор): джоба шага 2 (изучение конкурента, источники)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 13:49:57 +03:00
Дмитрий 1de6984035 feat(автоподбор): джоба шага 1 (подбор конкурентов)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 13:46:42 +03:00
Дмитрий 4042890b0a feat(автоподбор): идемпотентное списание за прогон (bcmath, only-on-success)
- AutopodborChargeService::chargeForRun — DB::transaction + lockForUpdate
  на AutopodborRun (guard идемпотентности по balance_transaction_id) и Tenant;
  bcmath (bcsub/bccomp/bcmul), никаких float; throw InsufficientBalanceException
  до любых изменений баланса при нехватке средств.
- Миграция 2026_06_28_110100: расширяет CHECK constraint
  balance_transactions_type_check — добавляет 'autopodbor_charge'.
- Тест: 2 money-инварианта (идемпотентность + noop при нехватке).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 13:41:06 +03:00
Дмитрий 77498df63b feat(автоподбор): дедуп конкурентов/источников/проектов
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 13:35:38 +03:00
Дмитрий 6789879a2c feat(автоподбор): нормализатор домена/телефона + dedup-ключи
AutopodborNormalizer: domainHead (схема/www/путь/порт → голова),
phone (через PhoneNormalizer::normalize → 7xxxxxxxxxx без плюса),
sourceKey и competitorKey для дедупликации конкурентов и источников.
4 теста, 9 assertions, все GREEN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 13:32:23 +03:00
Дмитрий 3b9c1b8bdc feat(автоподбор): интерфейс движка CompetitorAgent + заглушка + binding
- CompetitorAgent interface: findCompetitors / studyCompetitor / resolveByName
- FakeCompetitorAgent: 4 конкурента, 5 источников, 1 кандидат по имени
- AutopodborServiceProvider: bind(CompetitorAgent → FakeCompetitorAgent)
- Регистрация провайдера в bootstrap/providers.php (Laravel 11+)
- Pest.php: extend TestCase для Unit/Autopodbor (контейнер в Unit-тестах)
- Тест: 1/1 PASS, 10 assertions

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 13:28:56 +03:00
Дмитрий 0a111d9f85 feat(автоподбор): DTO контракта движка (6 шт.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 13:20:10 +03:00
Дмитрий 3c2bb18537 feat(автоподбор): тип проводки autopodbor_charge + ключи настроек
- BalanceTransaction::TYPE_AUTOPODBOR_CHARGE = 'autopodbor_charge'
- сид-миграция 4 ключей system_settings (idempotent):
  autopodbor_enabled (bool, 0), autopodbor_price_search_rub (decimal, 0),
  autopodbor_price_study_rub (decimal, 0), autopodbor_max_competitors (int, 15)
- Unit + Feature тесты, оба PASS

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 13:16:59 +03:00
Дмитрий df19af99f9 feat(автоподбор): Eloquent-модели run/competitor/source
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 13:12:35 +03:00
Дмитрий b5c88b2f1d docs(автоподбор): план — выделенная тестовая БД liderra_testing_apk + RLS-харднинг конвенция
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 13:08:59 +03:00
Дмитрий 2de1f1e35f fix(автоподбор): RLS NULLIF-харднинг (v8.57) + CHANGELOG v8.58 для 3 таблиц
Политики tenant_isolation приведены к каноничной харднинг-форме
NULLIF(current_setting('app.current_tenant_id', true), '')::bigint
(после переноса на gitea/main с v8.57). Запись CHANGELOG для всех 3 таблиц.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 13:02:40 +03:00
Дмитрий cc73a70f9e feat(автоподбор): таблица autopodbor_sources + RLS
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 12:58:27 +03:00
Дмитрий 786f796223 feat(автоподбор): таблица autopodbor_competitors + RLS
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 12:58:26 +03:00
Дмитрий e7660edd79 feat(автоподбор): таблица autopodbor_runs + RLS
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 12:58:10 +03:00
Дмитрий 1fe071f203 docs(автоподбор): дизайн-документ + план реализации
Дизайн и пошаговый план фичи «Автоподбор конкурентов» (ИИ-агент находит
конкурентов и их источники). Движок — отдельной сессией, здесь розетка+заглушка.
План сверен с кодом: RLS app.current_tenant_id, tenant-контекст SET LOCAL,
тестовая БД liderra_testing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 12:57:27 +03:00
Дмитрий c92d498b57 feat(админка): экран Тенанты на серверную пагинацию/поиск/фильтры (масштаб 1000+)
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
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
Дмитрий 2911f3ac0e docs(ПИЛОТ): снимок 28.06 — починен тихо сломанный биллинг-сторож (RLS) + playwright durable
Accessibility (Pa11y live) / a11y (push) Has been cancelled
Свод за заход «закрывай хвосты»: разбор и фикс 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
Дмитрий 75dded78a1 fix(биллинг): sweep и reminder перебирают тенантов через BYPASSRLS + playwright в зависимостях
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
Корень: после переезда на 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
Дмитрий cab0347fd2 merge: Этап B+C — кликабельные группы Заказа + ссылки Здоровья + Открыть всё
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
2026-06-28 10:24:35 +03:00
Дмитрий b2f08f28d5 feat(дашборд): Этап B+C — кликабельные группы Заказа, ссылки Здоровья, «Открыть всё»
B: строки групп «Заказа» кликабельны → проекты у поставщика (поиск по источнику) +
кнопка «Открыть проекты у поставщика». C: подсистемы «Здоровья» кликабельны →
Инциденты/Система/Интеграция с поставщиком; «Финансы» → Биллинг/Все клиенты;
«Клиенты» → Все клиенты. Сквозная вложенность дашборда замкнута до источников.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 10:23:30 +03:00
Дмитрий 00d32ef182 merge: Этап A — сквозная вложенность Лиды до источника в main
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
2026-06-28 10:16:14 +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
Дмитрий 14bb8a017c merge: выбор периода (свой диапазон) в main
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
2026-06-28 09:56:15 +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
Дмитрий a43f3df4c1 merge: пункт Командный центр в левом меню админки
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
2026-06-28 08:43:14 +03:00