feat(sales): массовая смена площадки прогрева
POST /api/sales/ad-audience/channels — начальник меняет channels у отмеченных фирм разом (гейт denyIfNotHead, менеджеру 403). firms() теперь отдаёт channels в списке. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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<int>, 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 ──────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
@@ -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']);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user