Files
portal/app/tests/Feature/Sales/AdAudienceWarmingFieldsTest.php
T
Дмитрий e15a4d8a69 feat(прогрев ф2): тоггл «Греть здесь» и firmRow — на строки firm_channels
Фаза 2 Этап A, Task 8. toggle пишет строку firm_channels(channel, status=warming,
mode=funnel, warming_started_at=firm.warmup_started_at, warmed_times++) вместо
булева ch_*; off удаляет строку (инкремент двумя шагами, без raw). firmRow на
вкладке площадки считает state/warming_active по строке канала через
decideForChannelRow (нет строки или loaded → stopped/false). firms() eager-грузит
firmChannels (против N+1). Границы этапа A: warming_channels-бейджи, platformState,
channelColumn, allFirms и показ всех фирм — без изменений (ch_*, показ состава →
этап C). ch_* колонки не удаляются. Бамп baseline (+9 actingAs-ложняков).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 16:08:35 +03:00

214 lines
8.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
use App\Models\SalesAdAudienceFirm;
use App\Models\SalesAdAudiencePhone;
use App\Models\SalesAdAudiencePlatform;
use App\Models\SalesProspect;
use App\Models\SalesUser;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Tests\Concerns\SharesSupplierPdo;
/**
* Task 1 плана «кусок B»: firmRow() отдаёт нишу, дату запуска прогрева,
* менеджера (id + имя) и «состояние прогрева» (активен ли прогрев + список
* каналов, на которые фирма реально подписана) — для витрины на вкладках
* площадок. Аутентификация и создание сущностей — как в AdAudiencePlatformApiTest.
*
* SharesSupplierPdo обязателен: SalesAdAudienceFirm/Phone прибиты к
* pgsql_supplier (свой PDO), без шаринга DatabaseTransactions откатывал бы
* только default-соединение (SalesProspect/SalesUser) — записи текли бы
* из теста в тест.
*
* План: docs/superpowers/plans/... (кусок B, Task 1).
*/
uses(DatabaseTransactions::class, SharesSupplierPdo::class);
function warmingFieldsHead(): SalesUser
{
return SalesUser::create([
'name' => 'Начальник '.uniqid(),
'email' => 'wfhead'.uniqid().'@sales.local',
'password' => bcrypt('secret'),
'role' => 'head',
'is_active' => true,
]);
}
function warmingFieldsManager(string $name): SalesUser
{
return SalesUser::create([
'name' => $name,
'email' => 'wfmgr'.uniqid().'@sales.local',
'password' => bcrypt('secret'),
'role' => 'manager',
'is_active' => true,
]);
}
it('firmRow отдаёт нишу, дату запуска, менеджера и состояние прогрева', function () {
$head = warmingFieldsHead();
$manager = warmingFieldsManager('Таня Михалева');
$prospect = SalesProspect::create([
'sales_user_id' => $manager->id,
'assigned_by' => $head->id,
'stage' => 'new',
'source' => 'search',
'firm_name' => 'Гарант',
'city' => 'Екатеринбург',
]);
$firm = SalesAdAudienceFirm::create([
'firm_inn' => '6678048177',
'firm_name' => 'Гарант',
'city' => 'Екатеринбург',
'rubric' => 'Частные стоматологии',
'warmup_started_at' => now()->subDays(4),
'ch_yandex' => true,
'ch_vk' => false,
'ch_mts' => true,
'prospect_id' => $prospect->id,
'assigned_at' => now(),
]);
// Task 8: warming_active/state считаются по строке канала firm_channels(yandex, warming),
// а не по ch_yandex. warming_channels-бейджи по-прежнему по ch_* (этап C не тронут).
$firm->firmChannels()->create([
'channel' => 'yandex', 'status' => 'warming', 'mode' => 'funnel',
'warming_started_at' => $firm->warmup_started_at, 'warmed_times' => 1,
]);
SalesAdAudiencePhone::create([
'phone' => '79000000001',
'firm_id' => $firm->id,
'firm_inn' => '6678048177',
'state' => 'active',
'expires_at' => now()->addDays(10),
'added_at' => now(),
]);
$row = $this->actingAs($head, 'sales')
->getJson('/api/sales/warming/yandex/firms')
->assertOk()
->json('firms.0');
expect($row['rubric'])->toBe('Частные стоматологии');
expect($row['warmup_started_at'])->not->toBeNull();
expect($row['manager_id'])->toBe($manager->id);
expect($row['manager_name'])->toBe('Таня Михалева');
expect($row['warming_active'])->toBeTrue();
expect($row['warming_channels'])->toContain('yandex');
expect($row['warming_channels'])->toContain('mts');
expect($row['warming_channels'])->not->toContain('vk');
});
it('назначает менеджера платформо-независимым маршрутом ad-audience', function () {
$head = warmingFieldsHead();
$manager = warmingFieldsManager('Игорь Соколов');
$firm = SalesAdAudienceFirm::create([
'firm_inn' => '6679177538',
'firm_name' => 'Винлайт',
'warmup_started_at' => now()->subDay(),
]);
$this->actingAs($head, 'sales')
->postJson("/api/sales/ad-audience/firms/{$firm->id}/assign", ['sales_user_id' => $manager->id])
->assertOk()
->assertJsonStructure(['prospect_id']);
$firm->refresh();
expect($firm->prospect_id)->not->toBeNull();
$this->actingAs($head, 'sales')
->postJson("/api/sales/ad-audience/firms/{$firm->id}/assign", ['sales_user_id' => $manager->id])
->assertStatus(422);
});
it('ставит канал sms, если номер фирмы попал в отправленную рассылку', function () {
$head = warmingFieldsHead();
$firm = SalesAdAudienceFirm::create([
'firm_inn' => '6673240656',
'firm_name' => 'Новодент',
'warmup_started_at' => now()->subDay(),
'ch_yandex' => true,
]);
SalesAdAudiencePhone::create([
'phone' => '79220216061',
'firm_id' => $firm->id,
'firm_inn' => '6673240656',
'state' => 'active',
'expires_at' => now()->addDays(10),
'added_at' => now(),
]);
$campaignId = DB::connection('pgsql_supplier')->table('sales_sms_campaigns')->insertGetId([
'title' => 'т', 'body' => 'т', 'status' => 'done', 'created_by' => $head->id,
'created_at' => now(), 'updated_at' => now(),
]);
DB::connection('pgsql_supplier')->table('sales_sms_messages')->insert([
'campaign_id' => $campaignId, 'phone' => '79220216061', 'status' => 'sent',
]);
$row = $this->actingAs($head, 'sales')->getJson('/api/sales/warming/yandex/firms')->json('firms.0');
expect($row['warming_channels'])->toContain('sms');
});
it('не ставит sms в песочнице (fake_sent)', function () {
$head = warmingFieldsHead();
$firm = SalesAdAudienceFirm::create([
'firm_inn' => '6660000000', 'firm_name' => 'Песочница',
'warmup_started_at' => now()->subDay(), 'ch_yandex' => true,
]);
SalesAdAudiencePhone::create([
'phone' => '79000000099', 'firm_id' => $firm->id, 'firm_inn' => '6660000000',
'state' => 'active', 'expires_at' => now()->addDays(10), 'added_at' => now(),
]);
$cid = DB::connection('pgsql_supplier')->table('sales_sms_campaigns')->insertGetId([
'title' => 'т', 'body' => 'т', 'status' => 'done', 'created_by' => $head->id,
'created_at' => now(), 'updated_at' => now(),
]);
DB::connection('pgsql_supplier')->table('sales_sms_messages')->insert([
'campaign_id' => $cid, 'phone' => '79000000099', 'status' => 'fake_sent',
]);
$row = $this->actingAs($head, 'sales')->getJson('/api/sales/warming/yandex/firms')->json('firms.0');
expect($row['warming_channels'])->not->toContain('sms');
});
it('состояние строки считается по срокам ЗАПРОШЕННОЙ площадки', function () {
$head = warmingFieldsHead();
// Одна фирма на yandex+vk, прогрев начат 4 дня назад.
// yandex warmup 5 → ещё active; vk warmup 3 → уже погасла.
SalesAdAudiencePlatform::forCode('yandex')->update(['warmup_days' => 5]);
SalesAdAudiencePlatform::forCode('vk')->update(['warmup_days' => 3]);
$firm = SalesAdAudienceFirm::create([
'firm_name' => 'На обеих', 'warmup_started_at' => now()->subDays(4),
'ch_yandex' => true, 'ch_vk' => true,
]);
// Task 8: строки каналов — точка отсчёта заезда та же, что и warmup_started_at фирмы.
foreach (['yandex', 'vk'] as $ch) {
$firm->firmChannels()->create([
'channel' => $ch, 'status' => 'warming', 'mode' => 'funnel',
'warming_started_at' => $firm->warmup_started_at, 'warmed_times' => 1,
]);
}
SalesAdAudiencePhone::create([
'phone' => '79000000055', 'firm_id' => $firm->id,
'state' => 'active', 'expires_at' => now()->addDays(10), 'added_at' => now(),
]);
$yandexRow = $this->actingAs($head, 'sales')->getJson('/api/sales/warming/yandex/firms')->json('firms.0');
$vkRow = $this->actingAs($head, 'sales')->getJson('/api/sales/warming/vk/firms')->json('firms.0');
expect($yandexRow['state'])->toBe('active')
->and($yandexRow['warming_active'])->toBeTrue();
expect($vkRow['state'])->toBe('stopped')
->and($vkRow['warming_active'])->toBeFalse();
});