Ревью нашло: форма объявления (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>
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>