actingAs(..., 'sales'). Паттерн $this-> * (не test()->) — чтобы larastan понимал тип TestCase. */ uses(DatabaseTransactions::class, SharesSupplierPdo::class); function warmingHead(): SalesUser { return SalesUser::create([ 'name' => 'Начальник '.uniqid(), 'email' => 'warminghead'.uniqid().'@sales.local', 'password' => bcrypt('secret'), 'role' => 'head', 'is_active' => true, ]); } function warmingManager(): SalesUser { return SalesUser::create([ 'name' => 'Менеджер '.uniqid(), 'email' => 'warmingmgr'.uniqid().'@sales.local', 'password' => bcrypt('secret'), 'role' => 'manager', 'is_active' => true, ]); } it('отдаёт настройки именно запрошенной площадки', function () { SalesAdAudiencePlatform::forCode('vk')->update(['status' => 'waiting_volume']); $this->actingAs(warmingHead(), 'sales') ->getJson('/api/sales/warming/vk') ->assertOk() ->assertJsonPath('data.platform', 'vk') ->assertJsonPath('data.min_phones', 2000) ->assertJsonPath('data.status', 'waiting_volume'); }); it('неизвестную площадку отвергает', function () { $this->actingAs(warmingHead(), 'sales') ->getJson('/api/sales/warming/facebook') ->assertNotFound(); }); it('рубильник пишется в строку площадки', function () { $this->actingAs(warmingHead(), 'sales') ->patchJson('/api/sales/warming/vk', ['enabled' => true]) ->assertOk(); expect(SalesAdAudiencePlatform::forCode('vk')->enabled)->toBeTrue(); }); it('менеджеру доступ закрыт', function () { $this->actingAs(warmingManager(), 'sales') ->getJson('/api/sales/warming/yandex') ->assertForbidden(); }); it('на вкладке площадки видны только фирмы со строкой этого канала', function () { // Task C1: firms(vk) отдаёт только фирмы со строкой firm_channels(vk), // а не весь синхронизированный состав. Фирма без vk-строки на вкладку не попадает. $vkFirm = SalesAdAudienceFirm::create(['firm_name' => 'Только ВК', 'ch_vk' => true]); $vkFirm->firmChannels()->create([ 'channel' => 'vk', 'status' => 'loaded', 'mode' => 'funnel', 'warming_started_at' => now(), 'warmed_times' => 0, ]); $yandexFirm = SalesAdAudienceFirm::create(['firm_name' => 'Только Яндекс', 'ch_yandex' => true]); $yandexFirm->firmChannels()->create([ 'channel' => 'yandex', 'status' => 'loaded', 'mode' => 'funnel', 'warming_started_at' => now(), 'warmed_times' => 0, ]); $resp = $this->actingAs(warmingHead(), 'sales') ->getJson('/api/sales/warming/vk/firms') ->assertOk() ->assertJsonCount(1, 'firms'); expect($resp->json('firms.0.firm_name'))->toBe('Только ВК') ->and($resp->json('firms.0.ch_vk'))->toBeTrue(); }); it('переключатель добавляет и убирает фирму с площадки', function () { // Task 8: членство площадки теперь — строка firm_channels(vk), а не флаг ch_vk. $firm = SalesAdAudienceFirm::create(['firm_name' => 'Плавающая', 'warmup_started_at' => now()->subDay()]); $head = warmingHead(); $this->actingAs($head, 'sales') ->postJson("/api/sales/warming/vk/firms/{$firm->id}/toggle", ['on' => true]) ->assertOk(); expect($firm->firmChannels()->where('channel', 'vk')->where('status', 'warming')->count())->toBe(1); $this->actingAs($head, 'sales') ->postJson("/api/sales/warming/vk/firms/{$firm->id}/toggle", ['on' => false]) ->assertOk(); expect($firm->firmChannels()->where('channel', 'vk')->count())->toBe(0); }); it('отдаёт ВСЕ фирмы прогрева, без фильтра площадки (для модуля СМС)', function () { 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]); $this->actingAs(warmingHead(), 'sales') ->getJson('/api/sales/ad-audience/firms') ->assertOk() ->assertJsonCount(3, 'firms'); }); it('список всех фирм закрыт для менеджера', function () { $this->actingAs(warmingManager(), 'sales') ->getJson('/api/sales/ad-audience/firms') ->assertForbidden(); }); it('PATCH сроков пишет в строку площадки, не в state и не в другие площадки', function () { $head = warmingHead(); $this->actingAs($head, 'sales') ->patchJson('/api/sales/warming/yandex', ['in_work_days' => 20]) ->assertOk() ->assertJsonPath('data.durations.in_work_days', 20); expect(SalesAdAudiencePlatform::forCode('yandex')->in_work_days)->toBe(20) ->and(SalesAdAudiencePlatform::forCode('vk')->in_work_days)->toBe(14) // default, не тронут ->and(SalesAdAudiencePlatform::forCode('mts')->in_work_days)->toBe(14) ->and(SalesAdAudienceState::current()->in_work_days)->toBe(14); // state не тронут }); it('сроки площадок независимы: yandex=20, vk=5', function () { $head = warmingHead(); $this->actingAs($head, 'sales')->patchJson('/api/sales/warming/yandex', ['in_work_days' => 20])->assertOk(); $this->actingAs($head, 'sales')->patchJson('/api/sales/warming/vk', ['in_work_days' => 5])->assertOk(); $this->actingAs($head, 'sales')->getJson('/api/sales/warming/yandex')->assertJsonPath('data.durations.in_work_days', 20); $this->actingAs($head, 'sales')->getJson('/api/sales/warming/vk')->assertJsonPath('data.durations.in_work_days', 5); expect(SalesAdAudiencePlatform::forCode('yandex')->in_work_days)->toBe(20); }); it('PATCH days пишется в строку площадки', function () { $head = warmingHead(); $this->actingAs($head, 'sales') ->patchJson('/api/sales/warming/mts', ['days' => 45]) ->assertOk() ->assertJsonPath('data.days', 45); expect(SalesAdAudiencePlatform::forCode('mts')->days)->toBe(45); });