diff --git a/app/app/Http/Controllers/Api/Sales/SalesAdAudienceController.php b/app/app/Http/Controllers/Api/Sales/SalesAdAudienceController.php index d0224377..5de22d09 100644 --- a/app/app/Http/Controllers/Api/Sales/SalesAdAudienceController.php +++ b/app/app/Http/Controllers/Api/Sales/SalesAdAudienceController.php @@ -111,6 +111,7 @@ class SalesAdAudienceController extends Controller return [ 'id' => $firm->id, 'firm_name' => $firm->firm_name, + 'channels' => $firm->channels, 'city' => $firm->city, 'phones_count' => $firm->phones->count(), 'state' => $phone === null ? 'stopped' : $phone->state, @@ -169,6 +170,32 @@ class SalesAdAudienceController extends Controller return response()->json(['prospect_id' => $prospect->id]); } + /** + * POST /api/sales/ad-audience/channels — массовая смена площадки (только head). + * + * Начальник отмечает строки галочками и жмёт «В Яндекс» / «В ВК» / «В оба». + * Состав в Яндексе подтянется сам ближайшей ночной заливкой — она фильтрует + * номера по этому полю. + */ + public function channels(Request $request): JsonResponse + { + if (($resp = $this->denyIfNotHead($request)) !== null) { + return $resp; + } + + /** @var array{firm_ids: list, channels: string} $data */ + $data = $request->validate([ + 'firm_ids' => ['required', 'array', 'min:1'], + 'firm_ids.*' => ['required', 'integer'], + 'channels' => ['required', 'string', 'in:yandex,vk,both'], + ]); + + $updated = SalesAdAudienceFirm::whereIn('id', $data['firm_ids']) + ->update(['channels' => $data['channels']]); + + return response()->json(['updated' => $updated]); + } + // ── private ────────────────────────────────────────────────────────────── /** diff --git a/app/routes/web.php b/app/routes/web.php index 125933d7..f78c2b88 100644 --- a/app/routes/web.php +++ b/app/routes/web.php @@ -308,6 +308,8 @@ Route::middleware(['admin-db', 'auth:sales', 'sales-portal'])->prefix('api/sales // Task 7 (план v2): список фирм на прогреве + назначение менеджера (только head). Route::get('/ad-audience/firms', [SalesAdAudienceController::class, 'firms']); Route::post('/ad-audience/firms/{firm}/assign', [SalesAdAudienceController::class, 'assign'])->whereNumber('firm'); + // Task 5 (план выбора площадки прогрева): массовая смена площадки (только head). + Route::post('/ad-audience/channels', [SalesAdAudienceController::class, 'channels']); // managers, dashboard }); diff --git a/app/tests/Feature/Sales/AdAudienceScreenTest.php b/app/tests/Feature/Sales/AdAudienceScreenTest.php index 91a71a0c..924f5901 100644 --- a/app/tests/Feature/Sales/AdAudienceScreenTest.php +++ b/app/tests/Feature/Sales/AdAudienceScreenTest.php @@ -232,3 +232,50 @@ it('повторное назначение той же фирмы отверг ->postJson("/api/sales/ad-audience/firms/{$firm->id}/assign", ['sales_user_id' => $this->manager->id]) ->assertStatus(422); }); + +// ── Task 5: массовая смена площадки прогрева ──────────────────────────────── + +it('начальник меняет площадку сразу у нескольких фирм', function () { + $first = SalesAdAudienceFirm::create([ + 'firm_name' => 'Первая', 'warmup_started_at' => now(), 'channels' => 'yandex', + ]); + $second = SalesAdAudienceFirm::create([ + 'firm_name' => 'Вторая', 'warmup_started_at' => now(), 'channels' => 'yandex', + ]); + + $this->actingAs($this->head, 'sales') + ->postJson('/api/sales/ad-audience/channels', [ + 'firm_ids' => [$first->id, $second->id], + 'channels' => 'both', + ]) + ->assertOk() + ->assertJson(['updated' => 2]); + + expect($first->fresh()->channels)->toBe('both') + ->and($second->fresh()->channels)->toBe('both'); +}); + +it('менеджеру менять площадку нельзя', function () { + $firm = SalesAdAudienceFirm::create([ + 'firm_name' => 'Чужая', 'warmup_started_at' => now(), + ]); + + $this->actingAs($this->manager, 'sales') + ->postJson('/api/sales/ad-audience/channels', [ + 'firm_ids' => [$firm->id], 'channels' => 'vk', + ]) + ->assertStatus(403); + + expect($firm->fresh()->channels)->toBe('yandex'); +}); + +it('в списке фирм видно, где греем', function () { + SalesAdAudienceFirm::create([ + 'firm_name' => 'Показательная', 'warmup_started_at' => now(), 'channels' => 'vk', + ]); + + $this->actingAs($this->head, 'sales') + ->getJson('/api/sales/ad-audience/firms') + ->assertOk() + ->assertJsonFragment(['channels' => 'vk']); +});