diff --git a/app/app/Http/Controllers/Api/Sales/SalesAdAudienceController.php b/app/app/Http/Controllers/Api/Sales/SalesAdAudienceController.php index b79f6692..4141eb78 100644 --- a/app/app/Http/Controllers/Api/Sales/SalesAdAudienceController.php +++ b/app/app/Http/Controllers/Api/Sales/SalesAdAudienceController.php @@ -29,6 +29,8 @@ use Symfony\Component\HttpFoundation\StreamedResponse; * POST /api/sales/warming/{platform}/firms/{id}/toggle — включить/выключить фирму на площадке * POST /api/sales/warming/{platform}/firms/{id}/assign — начальник назначает менеджера, рождается карточка воронки * GET /api/sales/warming/mts/file — выгрузка номеров для ручной загрузки в МТС + * GET /api/sales/ad-audience/firms — ВСЕ фирмы прогрева, без фильтра площадки + * (для модуля «Прогрев СМС», см. allFirms()) * * Все методы — ТОЛЬКО начальник (role=head), гейт зеркалит SalesManagersController. * Менеджер → 403. Неизвестная площадка (не yandex/vk/mts) → 404. @@ -236,6 +238,35 @@ class SalesAdAudienceController extends Controller ); } + /** + * GET /api/sales/ad-audience/firms — ВСЕ фирмы прогрева без фильтра площадки. + * + * Платформенный список — firms({platform}); этот метод отдаёт полный состав + * (используется модулем «Прогрев СМС», у которого своя логика отбора). + */ + public function allFirms(Request $request): JsonResponse + { + if (($resp = $this->denyIfNotHead($request)) !== null) { + return $resp; + } + + $firms = SalesAdAudienceFirm::query() + ->with('phones') + ->orderByRaw('ready_at IS NULL') + ->orderByDesc('warmup_started_at') + ->limit(500) + ->get(); + + $prospects = SalesProspect::query() + ->whereIn('id', $firms->pluck('prospect_id')->filter()->all()) + ->get() + ->keyBy('id'); + + return response()->json([ + 'firms' => $firms->map(fn (SalesAdAudienceFirm $firm): array => $this->firmRow($firm, $prospects))->all(), + ]); + } + // ── private ────────────────────────────────────────────────────────────── /** diff --git a/app/routes/web.php b/app/routes/web.php index 65ca4afc..7e9e11d3 100644 --- a/app/routes/web.php +++ b/app/routes/web.php @@ -316,6 +316,10 @@ Route::middleware(['admin-db', 'auth:sales', 'sales-portal'])->prefix('api/sales // Регистрируем ДО /warming/{platform}/firms — иначе .../firms не перепутается, // но mts/file имеет другое число сегментов, поэтому порядок не критичен. Route::get('/warming/mts/file', [SalesAdAudienceController::class, 'mtsFile']); + // Регрессия: старая /ad-audience/firms была снесена разъездом на площадки, но модуль + // «Прогрев СМС» (SalesSmsView) зовёт именно её для фирмо-пикера — восстановлена как + // платформо-независимый список ВСЕХ фирм прогрева (allFirms(), НЕ путать с firms()). + Route::get('/ad-audience/firms', [SalesAdAudienceController::class, 'allFirms']); // Task 9 (план «Прогрев СМС»): список рассылок, предпросмотр цены, запуск (только head). Route::get('/sms/campaigns', [SalesSmsController::class, 'index']); Route::post('/sms/campaigns', [SalesSmsController::class, 'store']); diff --git a/app/tests/Feature/Sales/AdAudiencePlatformApiTest.php b/app/tests/Feature/Sales/AdAudiencePlatformApiTest.php index 4ea1ec67..139742ce 100644 --- a/app/tests/Feature/Sales/AdAudiencePlatformApiTest.php +++ b/app/tests/Feature/Sales/AdAudiencePlatformApiTest.php @@ -97,3 +97,28 @@ it('переключатель добавляет и убирает фирму test()->postJson("/api/sales/warming/vk/firms/{$firm->id}/toggle", ['on' => false])->assertOk(); expect($firm->fresh()->ch_vk)->toBeFalse(); }); + +it('отдаёт ВСЕ фирмы прогрева, без фильтра площадки (для модуля СМС)', function () { + actingHead(); + + SalesAdAudienceFirm::create(['firm_name' => 'Только Яндекс', 'ch_yandex' => true]); + SalesAdAudienceFirm::create(['firm_name' => 'Только ВК', 'ch_vk' => true]); + SalesAdAudienceFirm::create(['firm_name' => 'Нигде', 'ch_yandex' => false, 'ch_vk' => false, 'ch_mts' => false]); + + test()->getJson('/api/sales/ad-audience/firms') + ->assertOk() + ->assertJsonCount(3, 'firms'); +}); + +it('список всех фирм закрыт для менеджера', function () { + $m = SalesUser::create([ + 'name' => 'Менеджер '.uniqid(), + 'email' => 'warmingmgr'.uniqid().'@sales.local', + 'password' => bcrypt('secret'), + 'role' => 'manager', + 'is_active' => true, + ]); + test()->actingAs($m, 'sales'); + + test()->getJson('/api/sales/ad-audience/firms')->assertForbidden(); +});