e23f5739bf
Фаза 2 Этап A, Task 3. Новый метод PlatformWarmingDecider::decideForChannelRow — решает по строке firm_channels: mode=flat считает простой срок от warming_started_at строки + flat_days (active/stopped(flat_done)); mode=funnel — та же логика decide(), но точка отсчёта warmup берётся из строки канала, а не из firm.warmup_started_at (у каждого канала свой заезд). Существующий decide() не тронут — Фаза 1 цела. AdAudienceScheduler без изменений. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
118 lines
4.6 KiB
PHP
118 lines
4.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\SalesAdAudienceFirm;
|
|
use App\Models\SalesAdAudienceFirmChannel;
|
|
use App\Models\SalesAdAudiencePlatform;
|
|
use App\Services\Sales\PlatformWarmingDecider;
|
|
use Carbon\CarbonImmutable;
|
|
use Illuminate\Support\Carbon;
|
|
use Tests\TestCase;
|
|
|
|
// Модели вне контейнера Laravel не могут выставить datetime-cast атрибут
|
|
// (setAttribute → fromDateTime → getConnection() падает без резолвера
|
|
// соединений — резолвер регистрируется только при загрузке приложения).
|
|
// Файл сам просит контейнер, не трогая общий tests/Pest.php.
|
|
uses(TestCase::class);
|
|
|
|
it('одна фирма: на площадке с большим сроком active, с меньшим — stopped(ready_for_manager)', function () {
|
|
$now = CarbonImmutable::parse('2026-07-22 12:00:00');
|
|
|
|
$firm = new SalesAdAudienceFirm(['firm_name' => 'X']);
|
|
$firm->warmup_started_at = Carbon::parse('2026-07-18 12:00:00'); // 4 дня назад
|
|
|
|
$decider = new PlatformWarmingDecider;
|
|
|
|
$long = new SalesAdAudiencePlatform(['platform' => 'yandex']);
|
|
$long->warmup_days = 5;
|
|
|
|
$short = new SalesAdAudiencePlatform(['platform' => 'vk']);
|
|
$short->warmup_days = 3;
|
|
|
|
expect($decider->decide($firm, $long, null, $now)['state'])->toBe('active');
|
|
|
|
$shortDecision = $decider->decide($firm, $short, null, $now);
|
|
expect($shortDecision['state'])->toBe('stopped');
|
|
expect($shortDecision['reason'])->toBe('ready_for_manager');
|
|
});
|
|
|
|
it('flat active: строка канала mode=flat, flat_days больше прошедшего срока — active', function () {
|
|
$now = CarbonImmutable::parse('2026-07-22 12:00:00');
|
|
|
|
$firm = new SalesAdAudienceFirm(['firm_name' => 'X']);
|
|
$platform = new SalesAdAudiencePlatform(['platform' => 'yandex']);
|
|
$platform->warmup_days = 3;
|
|
|
|
$row = new SalesAdAudienceFirmChannel([
|
|
'channel' => 'yandex',
|
|
'status' => 'warming',
|
|
'mode' => 'flat',
|
|
'flat_days' => 10,
|
|
]);
|
|
$row->warming_started_at = Carbon::parse('2026-07-18 12:00:00'); // 4 дня назад
|
|
|
|
$decider = new PlatformWarmingDecider;
|
|
|
|
$decision = $decider->decideForChannelRow($firm, $platform, null, $row, $now);
|
|
|
|
expect($decision['state'])->toBe('active');
|
|
});
|
|
|
|
it('flat done: строка канала mode=flat, flat_days меньше прошедшего срока — stopped(flat_done)', function () {
|
|
$now = CarbonImmutable::parse('2026-07-22 12:00:00');
|
|
|
|
$firm = new SalesAdAudienceFirm(['firm_name' => 'X']);
|
|
$platform = new SalesAdAudiencePlatform(['platform' => 'yandex']);
|
|
$platform->warmup_days = 3;
|
|
|
|
$row = new SalesAdAudienceFirmChannel([
|
|
'channel' => 'yandex',
|
|
'status' => 'warming',
|
|
'mode' => 'flat',
|
|
'flat_days' => 3,
|
|
]);
|
|
$row->warming_started_at = Carbon::parse('2026-07-17 12:00:00'); // 5 дней назад
|
|
|
|
$decider = new PlatformWarmingDecider;
|
|
|
|
$decision = $decider->decideForChannelRow($firm, $platform, null, $row, $now);
|
|
|
|
expect($decision['state'])->toBe('stopped');
|
|
expect($decision['reason'])->toBe('flat_done');
|
|
});
|
|
|
|
it('funnel из строки: срок берётся из warming_started_at строки канала, не из firm.warmup_started_at', function () {
|
|
$now = CarbonImmutable::parse('2026-07-22 12:00:00');
|
|
|
|
$firm = new SalesAdAudienceFirm(['firm_name' => 'X']);
|
|
// Умышленно НЕ ставим firm->warmup_started_at — decideForChannelRow должен
|
|
// взять срок из строки канала, а не из фирмы.
|
|
|
|
$platform = new SalesAdAudiencePlatform(['platform' => 'yandex']);
|
|
$platform->warmup_days = 3;
|
|
|
|
$decider = new PlatformWarmingDecider;
|
|
|
|
$rowActive = new SalesAdAudienceFirmChannel([
|
|
'channel' => 'yandex',
|
|
'status' => 'warming',
|
|
'mode' => 'funnel',
|
|
]);
|
|
$rowActive->warming_started_at = Carbon::parse('2026-07-20 12:00:00'); // 2 дня назад
|
|
|
|
$activeDecision = $decider->decideForChannelRow($firm, $platform, null, $rowActive, $now);
|
|
expect($activeDecision['state'])->toBe('active');
|
|
|
|
$rowStopped = new SalesAdAudienceFirmChannel([
|
|
'channel' => 'yandex',
|
|
'status' => 'warming',
|
|
'mode' => 'funnel',
|
|
]);
|
|
$rowStopped->warming_started_at = Carbon::parse('2026-07-17 12:00:00'); // 5 дней назад
|
|
|
|
$stoppedDecision = $decider->decideForChannelRow($firm, $platform, null, $rowStopped, $now);
|
|
expect($stoppedDecision['state'])->toBe('stopped');
|
|
expect($stoppedDecision['reason'])->toBe('ready_for_manager');
|
|
});
|