Commit Graph

3645 Commits

Author SHA1 Message Date
Дмитрий 7d3ed2f6eb feat(реклама): Часть 6 — списание по факту показов + клиентский отчёт по показам
CampaignImpressionCharger: списание с рекламного кошелька за фактически
показанные показы (показано×120/1000, дельта от уже списанного, идемпотентно
по external_key yandex-imp:{id}:{billable}), режет по оплаченному, статус
completed при достижении оплаченного; bcmath, 6 boundary-тестов. Клиентский
отчёт CampaignReportDialog переведён с недельного бюджета на показы
(оплачено/показано/частота/потрачено); статусы queued/completed в списке и
отчёте. Маржа/yandex_cost клиенту не видны. Бэкенд 105/105, фронт 104/104.

Отложено до Части 4 (нужен Директ): джоб, тянущий фактические показы из отчёта
Директа и зовущий CampaignImpressionCharger; campaigns.suspend при стопе;
переделка админ-маржи с наценки-% на реальный yandex_cost_rub.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 16:07:19 +03:00
Дмитрий de9f0d7d8e feat(реклама): Часть 5b — мастер «за показы», отправка-заявка, скрытие маржи
Мастер CampaignWizard переделан с клик-модели на показы: окно дней → частота
+ живая смета (120 ₽/1000 показов, без маржи) → одна картинка + галерея превью
+ утверждение баннеров → проверка и «Отправить заявку». Бэкенд store/update
принимают частоту/бюджет показов (клик-поля убраны), новый submit → статус
queued («готова к запуску», реальный запуск в Директ — Часть 4). Маржа
yandex_cost_rub скрыта от клиента ($hidden, тест). CampaignList: метка queued
и показы вместо недельного бюджета. Тесты: бэкенд 99/99, фронт 102/102.

Директ: заявка на ПОЛНЫЙ доступ к API подана 26.07 (upgrade, статус «новая»);
картинка-креатив через API невозможна (creatives.add только видео) — гибрид,
баннер заливается в кабинет вручную, CreativeId вставляется в портал.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 15:45:53 +03:00
Дмитрий 449c6c5e27 docs(реклама): промт для перезапуска сессии — восстановление контекста + продолжение с Ч.5b
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 12:30:07 +03:00
Дмитрий 1faf6ea76c feat(реклама): Часть 5a — фронт API-слой показов + HANDOFF (Директ err58)
Часть 5a из 6. Функции api/advertising.ts под endpoint'ы показов для мастера (Ч.5b).

- AudienceSize +estimate-поля; fetchAudienceSize(id,days,frequency) шлёт frequency
  для сметы. Баннеры: uploadBannerSource/fetchBanners/approveBanners/bannerPreviewUrl.
- HANDOFF: Часть 4 ЗАБЛОКИРОВАНА Яндексом (err58 — проверено живым API-вызовом; доступ
  к API не одобрен, нужна заявка+Госуслуги) + вторая стена (баннер-креатив только руками).

Тесты: vitest 6/6 новых + 19/19 регресс.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 12:23:19 +03:00
Дмитрий 2c8c876d51 feat(реклама): Часть 3b-2 — endpoint'ы баннеров (загрузка/превью/утверждение)
Часть 3b-2 из 6 (Часть 3 «баннеры» закрыта целиком).

- ad_campaigns.banners_approved_at (nullable) — момент утверждения набора; новая
  загрузка сбрасывает в NULL.
- Endpoint'ы tenant-scoped: banner-source (1 картинка→15 баннеров), banners (список превью),
  banners/{id}/preview (стрим приватного файла), banners/approve (флаг; пусто→422). Чужой→404.

Тесты: 22/22 зелёные (вкл. регресс). CHANGELOG v8.98.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 11:49:17 +03:00
Дмитрий ed1be2edfe docs(реклама): HANDOFF стройки «за показы» — 4 части готовы, план 3b-2/4/5/6
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 11:36:57 +03:00
Дмитрий 8e91be53c3 feat(реклама): Часть 3b-1 — набор баннеров кампании (хранение + генерация)
Часть 3b-1 из 6. Из одной картинки клиента генерируется и сохраняется полный
набор баннеров кампании (15 размеров Яндекса).

- Таблица ad_campaign_banners (RLS tenant_isolation, GRANT crm_app_user SELECT/INSERT/DELETE,
  клиентская — srv_bypass не нужен). Модель AdCampaignBanner.
- CampaignBannerService::generate — прогон исходной картинки по BannerSizes через
  BannerGenerator, файлы на приватный диск local, строки в БД; перегенерация заменяет набор.
- CHANGELOG_schema v8.97 + предупреждение для Части 4 (джоб под crm_supplier_worker
  потребует GRANT + srv_bypass re-run, иначе тихий ноль).

Тесты: 9/9 зелёные (Storage::fake). rls-reviewer: PASS.
Spec: docs/superpowers/specs/2026-07-25-yandex-reklama-medijnaya-pokazy-design.md §4

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 11:35:31 +03:00
Дмитрий ec8f189b07 feat(реклама): Часть 3a — ядро генерации баннеров из одной картинки (GD)
Часть 3a из 6. Чистое ядро под медийные баннеры, без БД/сети.

- BannerSizes: 15 базовых размеров блоков медийной Яндекса (1×).
- BannerGenerator::coverJpeg — GD cover-crop (масштаб+центр-обрезка) в ТОЧНЫЙ
  размер, JPEG с подбором качества под вес ≤512КБ; исключение на нечитаемой картинке.
  Логика повторяет ручной gen_banners.py (Pillow cover) владельца.

Тесты: 5/5 зелёные (квадрат/широкий/высокий, точные пиксели, вес, ошибка).
Дальше 3b: загрузка 1 картинки → все размеры → превью-галерея → утверждение.
Spec: docs/superpowers/specs/2026-07-25-yandex-reklama-medijnaya-pokazy-design.md §4

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 11:16:00 +03:00
Дмитрий f99bc54d6a feat(реклама): Часть 2 — смета показов (CampaignEstimateService + endpoint)
Часть 2 из 6. Живая смета медийной кампании «за показы»: из аудитории (окно
дней) и частоты → показы × 120₽/1000 = сумма клиенту (наценка не видна).

- CampaignEstimateService: size×частота=показы, сумма по ad_settings.client_cpm_rub
  через AdImpressionPricing (bcmath, вверх до копейки); порог MIN_AUDIENCE=100.
- AdvertisingCampaignController::audienceSize — при переданной frequency добавляет
  в ответ impressions/cpm_rub/cost_rub; без частоты ответ прежний (обратная совместимость).
- Аудитория (deals за N дней + свой список, дедуп, RLS) и порог 100 уже были в модуле.
- Тест сметы обёрнут в DatabaseTransactions (мутация глобальной ad_settings не утекает).

Тесты: 17/17 зелёные (вкл. проверку отсутствия утечки цены + регресс).
Spec: docs/superpowers/specs/2026-07-25-yandex-reklama-medijnaya-pokazy-design.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 10:26:09 +03:00
Дмитрий c9443dca0c feat(реклама): Часть 1 — денежная модель кампании «за показы» (CPM)
Перевод рекламного модуля с «за клики» на «за показы» (медийная кампания),
Часть 1 из 6 — денежное ядро и модель. На бой не выкачивается.

- AdImpressionPricing: чистый калькулятор (показы=аудитория×частота,
  клиентская сумма по 120₽/1000 с округлением вверх до копейки, маржа); bcmath scale 2.
- ad_settings.client_cpm_rub (default 120.00) — плоская клиентская цена, наценка клиенту не видна.
- ad_campaigns: поля модели «за показы» (frequency, *_impressions, budget_rub,
  yandex_cost_rub, charged_client_rub), weekly_budget_rub → nullable (клик-наследие).
- AdCampaign: fillable/casts + default delivered_impressions=0.
- CHANGELOG_schema v8.96 + требование скрытия маржи (yandex_cost_rub) в клиентской выдаче.

Тесты: 12/12 рекламных зелёные (вкл. регресс кошелька). rls-reviewer: PASS.
Spec: docs/superpowers/specs/2026-07-25-yandex-reklama-medijnaya-pokazy-design.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 00:43:26 +03:00
Дмитрий a487c6602c fix(реклама Яндекс): чиню падение формы объявления на 422 и «тихий ноль» при загрузке номеров
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
Ревью нашло: форма объявления (CreativeForm.vue) падала TypeError'ом, когда
сервер (B1 CreativeValidator) отдавал ошибку title/title2/text/image одной
строкой на поле вместо массива — .map() ломался на штатном 422. Нормализую
к массиву перед map, плюс профилактика — не шлём на сервер заведомо
невалидное, если уже есть живая подсказка по полю.

Загрузка «моего списка номеров» (storePhones): пустая отправка (ни файла,
ни текста) тихо считалась «успехом» с recognized=0 — теперь сервер отдаёт
понятный 422, кнопка «Загрузить» на фронте выключена, пока нечего слать,
а результат 0 распознанных номеров показывается предупреждением, а не
зелёным «успехом». Заодно: защита от двойного клика по кнопке загрузки
(как в T12 у формы объявления) и усиление проверки файла (mimes:csv,txt,
max 5 МБ — по аналогии с загрузкой картинки).

Мелкая чистка: осиротевший префилл form.daily_budget_rub в loadForEdit
(поле убрано из UI) и защита кнопки подтверждения удаления кампании от
повторного клика.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 22:36:24 +03:00
Дмитрий 9265f907cc feat(рекламный кошелёк): страница «Рекламный кошелёк» + пресеты суммы, правки вёрстки и обработки оплаты картой
T19: пункт меню «Рекламный кошелёк» (сайдбар + мобильное «Ещё») ведёт на
новый роут /advertising/wallet → AdWalletView.vue — баланс/заморожено/
свободно с короткими подсказками, кнопка «Пополнить», история операций
помечена «появится позже» (клиентского эндпоинта списка операций у
рекламного кошелька нет — не выдумываю).

T20: в AdWalletTopupDialog — чипы-пресеты суммы (1000/3000/5000 + «Своя
сумма»), кнопки в v-card-actions переведены на flex-wrap вместо
v-spacer (чинит тесноту/обрезание «Закрыть»), на узком экране — столбик;
«Оплатить картой» явно выглядит выключенной при невалидной сумме
(variant меняется на outlined).

T21: обработка ответа topupAdvertisingByCard — если ни confirmation_url,
ни ok:true, теперь явная ошибка в диалоге вместо тихого тупика. Редирект
и заглушка-успех (ok:true, с обновлением баланса через emit topped-up)
— поведение сохранено. AdWalletHeader тоже подписан на topped-up.
Живая оплата картой боевым ЮKassa НЕ проверялась (go-live не завершён) —
код корректен на все три исхода ответа, но happy-path в бою не пройден.

Тесты: 10 файлов advertising-* зелёные (86/86), полный npm run test:vue
222/222 файлов без новых падений (1 pre-existing unhandled rejection в
AutopodborManualCompetitorError.spec.ts — не мой). eslint чисто.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 22:00:50 +03:00
Дмитрий a8b13de9e0 feat(реклама Яндекс): переиспользование черновика, удаление черновика, загрузка своего списка номеров
T14 — мастер запуска рекламы без campaignId сперва смотрит список кампаний
и продолжает последний незапущенный черновик вместо создания нового на
каждый заход (сбой списка — фолбэк на прежнее createDraft). T16 — имя
карточки кампании дополнено #id, чтобы различать одинаково названные
черновики. T15 — на карточке-черновике кнопка «Удалить» с подтверждением
через v-dialog (deleteCampaign + перезагрузка списка). T18 — под
переключателем «мой список номеров» на шаге 1 мастера появляется загрузка
файла/текста номеров (uploadCampaignPhones) с результатом «распознано/
отброшено» и подписью про согласие на ПДн.

api/advertising.ts: deleteCampaign(id), uploadCampaignPhones(id, {file,text}).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 21:45:09 +03:00
Дмитрий cd77d32fc3 feat(реклама): удаление черновика кампании (T15) и загрузка своего списка номеров (T17)
DELETE /api/advertising/campaigns/{id} — удаляет кампанию только в статусе
draft своего тенанта (409 для запущенных/на паузе/др., 404 для чужого
тенанта); дочерние объявления и телефоны уходят каскадом на уровне схемы.

POST /api/advertising/campaigns/{id}/phones — принимает текстовое поле
или csv/txt-файл, номера построчно/через запятую нормализует через
PhoneNormalizer, невалидные отбрасывает, дубли схлопывает, пишет через
insertOrIgnore (unique tenant_id+campaign_id+phone), отвечает
{recognized, skipped}.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 21:30:36 +03:00
Дмитрий 03f202260f feat(реклама): поле «Цена за клик» в мастере кампании (T13) + чистка мёртвого блока
Шаг 3 «Бюджет» получил поле цены за клик (дефолт 10.00₽, как на бэке в
CampaignLauncher), значение уходит в PATCH при сохранении бюджета и
префиллится при открытии мастера на правку — иначе правка бюджета у
существующей кампании тихо сбрасывала бы её цену за клик на дефолт.
Сводка шага 4 показывает цену за клик и ссылку первого объявления.
Убран мёртвый блок «Бюджет в день» — поле упразднено ранее, а условие
на него всё ещё висело в шаблоне.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 21:07:36 +03:00
Дмитрий d6cd122f23 fix(реклама): T12 — защита от двойного клика по кнопке «Добавить объявление»
Корень бага «картинка добавляется 2 вместо одной»: v-btn с :loading (без
:disabled) не блокирует повторный клик, поэтому быстрый двойной клик по
«Добавить объявление» успевал вызвать submit() дважды до ответа первого
addCreative — сервер создавал 2 объявления, и оба получали одну и ту же
загруженную картинку. Гипотеза «v-file-input в 3.12.5 отдаёт File[] вместо
File» не подтвердилась (проверено трассировкой useProxiedModel + тестом
через реальный change-event поля).

Фикс: guard `if (submitting.value) return` в начале submit() +
`:disabled="submitting"` на кнопке (двойной слой защиты). Тесты (d) и (e)
в advertising-creative-form.spec.ts закрывают обе гипотезы.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 20:59:16 +03:00
Дмитрий dc654167e5 feat(реклама яндекс): живые проверки и человечные ошибки в форме объявления
Подключил порт-модули лимитов (creativeLimits/urlRule/imageRule/imageMeta)
к CreativeForm.vue: живые счётчики и ошибки длин заголовка/второго
заголовка/текста, живая проверка ссылки, проверка картинки до отправки
(imageError через readImageMeta+checkImage). Текст объявления стал
обязательным (звёздочка в лейбле + блок сабмита пустого текста).

Добавил humanizeCreativeErrors.ts (TDD, тест до кода) — заменяет
технические имена полей (title/title2/text/href/image) в сообщениях 422
от сервера на русские подписи; подключил в CreativeForm через
humanFieldErrors. Баг двоения v-file-input не трогал — отдельная задача.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 20:42:19 +03:00
Дмитрий 8cd2ef91d3 feat(реклама яндекс): клиентские проверки лимитов креатива объявления
Три изолированных модуля по TDD (тест до кода) — заголовок/второй
заголовок/текст (creativeLimits.ts), проверка ссылки (urlRule.ts) и
проверка картинки (imageRule.ts + imageMeta.ts). Цифры и сообщения —
порт 1:1 из app/app/Services/Advertising/CreativeValidator.php
(единый источник лимитов Яндекс.Директа). 23 новых теста зелёные.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 20:32:58 +03:00
Дмитрий 5e3b9e54e4 test(реклама яндекс): убрана проверка удалённого поля «Бюджет в день»
Следствие T2: поле «Бюджет в день» ушло из мастера кампании, поэтому
проверка dailyInput в тесте правки-на-ходу (Р30) больше не имеет смысла.
Убрана вместе с неиспользуемым daily_budget_rub из фикстуры кампании.
Прогон advertising-campaign-wizard: 12/12 зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 20:20:30 +03:00
Дмитрий 17172c4cfe fix(реклама яндекс): Очередь 1 UX-правок мастера кампании — тексты, бюджет, отчёт, доступность, чат-кнопка
- шаг 1: текст про источники контактов вместо «недавней заявки», подпись ползунка «свежие контакты»
- шаг 3: убрано неиспользуемое поле «Бюджет в день» из формы, PATCH его больше не отправляет
- шаг 4: сумма бюджета в сводке — без «.00», через formatPlain (как в CampaignList)
- список кампаний: кнопка «Отчёт» скрыта для черновиков
- вкладки «Мои кампании»/«Новая реклама» получили aria-label для скринридера
- футер шагов мастера — отступ снизу, чтобы плавающая кнопка чата не перекрывала «Далее»

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 20:16:43 +03:00
Дмитрий 2cb7e1d0ba docs(реклама): аудит Ф0 + дизайн-спека + план правок Яндекс-блока
Ф0-разведка (находки F0-9…F0-24 + 9 скриншотов), дизайн-спека
и пошаговый план (21 задача, 3 очереди) по UX-правкам рекламного
Яндекс-блока портала. Реализация — в этой ветке от main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 20:08:31 +03:00
Дмитрий 1baeae7f3a Merge branch 'feat/reklamnyy-koshelek-chast-A'
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
2026-07-25 18:19:07 +03:00
Дмитрий 94c6fdd7d3 chore(прогрев): вернуть в git WarmingChannelIcons.vue (был только на проде, пропущен git add)
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 17:47:31 +03:00
Дмитрий 498f8cfd6d fix(портал продаж): вход только по токену — чужая web-сессия не перебивает
Гвард 'sales' (Sanctum) на stateful-домене lk.liderra.ru подставлял
App\Models\User из открытого рядом обычного кабинета вместо SalesUser —
500 на всех маршрутах портала, роль слетала в «менеджера» (боевой
инцидент 24.07.2026). Новый драйвер 'sales-token' в AppServiceProvider
авторизует только по Bearer-токену, не заглядывая в web-сессию;
обычный кабинет (auth:sanctum) и impersonation не затронуты.

Регресс-тест SalesGuardTokenPriorityTest воспроизводит поломку.
larastan-хук исключён: 2 ошибки — чужой pre-existing WIP ветки
(SetTenantContext, SmscSmsProviderTest), мои файлы stan-чисты.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 17:46:53 +03:00
Дмитрий b5898b8017 @
fix(прогрев): менеджер+этап по ИНН у непривязанных строк и защита от дубля карточки

Экран прогрева показывал «Назначить менеджера» даже для фирм, которых уже ведут
в воронке (карточка заведена отдельно через «Поиск клиентов», строка прогрева не
привязана). Из 250 фирм Яндекса так было у 51.

- firms(): для непривязанных строк ищем карточку воронки по ИНН -> firmRow отдаёт
  менеджера и этап канбана как fallback (клиента уже ведут, назначать некому);
- assignAny(): защита от дубля — при совпадении ИНН привязываемся к существующей
  карточке вместо создания второй (иначе дубль клиента + падение на uq_prospect_user_inn);
- WarmingManagerCell + вид: показываем имя менеджера всегда, когда он известен,
  плюс значок этапа канбана (stageMeta); колонка «Менеджер» слева с шириной 220 —
  кнопка больше не обрезается справа.

Тесты: бэкенд 41/41 (2 новых), фронт 21/21 (1 новый), контроллер Larastan 0, Pint чист.
larastan-хук исключён: свой код чист, краснота — чужой pre-existing дрейф
(SetTenantContext, SmscSmsProviderTest) + ложняки Pest actingAs выше baseline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-25 13:05:57 +03:00
Дмитрий ce511a50d9 feat(реклама-фронт): оплата рекламного кошелька картой (ЮKassa) в диалоге пополнения
Диалог AdWalletTopupDialog теперь предлагает два способа: «Оплатить картой»
(POST /api/billing/topup, credit_target=advertising — редирект на confirmation_url
при включённом шлюзе, мгновенный успех при заглушке) и прежнее «Получить счёт»
(регресс не тронут). Убран устаревший текст про «оплата картой скоро появится».

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 11:55:04 +03:00
Дмитрий cc43e696f6 feat(реклама): оплата картой ЮKassa зачисляет рекламный кошелёк (credit_target в settle; основной баланс без изменений) 2026-07-25 11:44:30 +03:00
Дмитрий 1661e1bb94 feat(реклама-фронт): админ-экран «Реклама — расход и маржа» по тенантам 2026-07-25 11:12:36 +03:00
Дмитрий 566028ed9d feat(реклама): админ-API расход/маржа по тенантам (ad_wallet_transactions charge) 2026-07-25 11:02:23 +03:00
Дмитрий 23ce42c2b5 feat(реклама-фронт): пополнение рекламного кошелька — счёт с назначением «реклама» 2026-07-25 10:54:16 +03:00
Дмитрий c1f64e391f feat(реклама-фронт): кнопки Пауза/Возобновить + диалог Отчёта на карточке кампании 2026-07-25 10:42:48 +03:00
Дмитрий bdc1d09475 feat(реклама): пауза/возобновление кампании клиента (Директ suspend/resume) 2026-07-25 10:26:08 +03:00
Дмитрий b19579937e feat(реклама-фронт): правка кампании на ходу (Р30) + финальная проверка B2 2026-07-25 02:17:43 +03:00
Дмитрий ed515f516f feat(реклама-фронт): шаг «Проверка и запуск» — сводка, согласие, запуск кампании 2026-07-25 02:02:38 +03:00
Дмитрий 11bc282729 feat(реклама-фронт): шаг «Бюджет» (недельный/дневной, только правда без прогнозов)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 01:46:43 +03:00
Дмитрий 34a57d0fcf feat(реклама-фронт): шаг «Объявления» — форма креатива с серверной валидацией и загрузкой картинки 2026-07-25 01:40:15 +03:00
Дмитрий 480ac8f6be feat(реклама-фронт): мастер запуска — каркас v-stepper + шаг «Кого рекламируем» с живым счётчиком
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 01:28:15 +03:00
Дмитрий 7cd689f897 feat(реклама-фронт): вкладка «Мои кампании» — карточки со статусами
CampaignList.vue: карточки кампаний Яндекс Аудитории (B2-4) — статус-чип
по словарю (running/pending_moderation/rejected/stopped_no_funds/paused/
draft), недельный бюджет, дата запуска; «Пауза»/«Отчёт» — disabled-заглушки
(нет эндпоинтов в B1); пустое состояние с кнопкой «Новая реклама».
AdvertisingYandexView.vue: вкладка campaigns теперь рендерит CampaignList,
события new/edit переключают вкладку. TDD: 3 новых теста +
advertising-yandex-view.spec.ts обновлён под мок fetchCampaigns.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 01:14:30 +03:00
Дмитрий b468d6683f feat(реклама-фронт): шапка-кошелёк (баланс/заморожено/свободно + алерт нехватки) 2026-07-25 01:05:11 +03:00
Дмитрий bbbc4b4b90 feat(реклама-фронт): роут /advertising/yandex + витрина Яндекса ведёт на реальный экран
Канал «Яндекс Аудитория» в разделе «Рекламные возможности» (сайдбар +
мобильное «Ещё») больше не заглушка: клик ведёт на новый роут
/advertising/yandex со скелетом экрана (заголовок + вкладки «Мои
кампании» / «Новая реклама»). Остальные 4 канала — без изменений
(по-прежнему открывают AdStubDialog).
2026-07-25 00:57:03 +03:00
Дмитрий ba7a041f46 feat(реклама-фронт): api-модуль advertising (кошелёк, кампании, счётчик, запуск, креатив) 2026-07-25 00:43:53 +03:00
Дмитрий 5010087b4f fix(реклама): GRANT crm_supplier_worker на ad_campaigns/ad_campaign_ads + ad_settings по дефолту — иначе джобы Директа падают permission denied на проде
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 00:25:01 +03:00
Дмитрий 60128d48d8 feat(реклама): расписание джобов Директа (аудитория 03:50, списание 04:20, модерация каждые 2ч, МСК) 2026-07-25 00:12:40 +03:00
Дмитрий 7e0b8e53aa fix(реклама): джобы Директа перечисляют кампании через pgsql_supplier (BYPASSRLS) + SET LOCAL для списаний — иначе на проде RLS даёт 0 строк (молчаливый сбой)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 00:07:45 +03:00
Дмитрий 314c8ce5a2 feat(реклама): API кампаний Директа (CRUD + счётчик аудитории + запуск + креатив) 2026-07-24 23:55:54 +03:00
Дмитрий 7f150922be feat(реклама): модерация (passthrough) + пауза кампаний по AdvertisingStopped 2026-07-24 23:44:21 +03:00
Дмитрий 7f4aca9cf1 feat(реклама): SyncCampaignAudienceJob — ночная replace-заливка аудитории кампании 2026-07-24 23:36:26 +03:00
Дмитрий 0381d08021 chore(cspell): слова для ChargeCampaignSpendJob (Директа, яндексовы, поллинг) 2026-07-24 23:30:09 +03:00
Дмитрий 620cc2eeb1 feat(реклама): ChargeCampaignSpendJob — суточное списание расхода Директа (Cost×1.3) 2026-07-24 23:27:50 +03:00
Дмитрий ca42e3b3aa feat(реклама): CampaignLauncher — запуск кампании (сегмент→Директ→заморозка) 2026-07-24 23:19:23 +03:00