Files
portal/app/tests/Feature/Sales/AdAudienceWarmingFieldsTest.php
T
Дмитрий 10ea2f6295 feat(прогрев): строка фирмы показывает состояние по срокам площадки
state/stop_reason/resume_at/warming_active на вкладке площадки
(GET /api/sales/warming/{platform}/firms) теперь считаются
PlatformWarmingDecider по срокам ИМЕННО этой площадки, а не сводкой
по phone.state/firm.stopped_at — та же фирма может быть active на
yandex и уже stopped на vk. allFirms() (СМС-подборщик) сохраняет
старую сводную логику без изменений.
2026-07-22 11:27:09 +03:00

200 lines
7.9 KiB
PHP

<?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(),
]);
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,
]);
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();
});