194 lines
6.6 KiB
PHP
194 lines
6.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Services\Sales\AdAudienceScheduler;
|
|
use Carbon\CarbonImmutable;
|
|
|
|
/** Сроки по умолчанию из спеки v2. */
|
|
function durations(array $override = []): array
|
|
{
|
|
return array_merge([
|
|
'warmup_days' => 3,
|
|
'new_days' => 3,
|
|
'in_work_days' => 14,
|
|
'negotiation_overdue_days' => 3,
|
|
'negotiation_far_threshold_days' => 14,
|
|
'negotiation_far_head_days' => 3,
|
|
'negotiation_far_lead_days' => 3,
|
|
'no_answer_days' => 7,
|
|
'rejected_days' => 3,
|
|
'registered_days' => 30,
|
|
'testing_days' => 30,
|
|
], $override);
|
|
}
|
|
|
|
function decide(array $firm, array $override = [], string $now = '2026-08-01 12:00:00'): array
|
|
{
|
|
return (new AdAudienceScheduler(durations($override)))
|
|
->decide($firm, CarbonImmutable::parse($now));
|
|
}
|
|
|
|
it('греет фирму без менеджера три дня', function () {
|
|
$result = decide([
|
|
'stage' => null,
|
|
'warmup_started_at' => CarbonImmutable::parse('2026-07-31 12:00:00'),
|
|
'next_call_at' => null,
|
|
]);
|
|
|
|
expect($result['state'])->toBe('active');
|
|
});
|
|
|
|
it('останавливает прогрев и зовёт назначить менеджера, когда три дня вышли', function () {
|
|
$result = decide([
|
|
'stage' => null,
|
|
'warmup_started_at' => CarbonImmutable::parse('2026-07-25 12:00:00'),
|
|
'next_call_at' => null,
|
|
]);
|
|
|
|
expect($result['state'])->toBe('stopped')
|
|
->and($result['reason'])->toBe('ready_for_manager');
|
|
});
|
|
|
|
it('крутит рекламу на новой карточке три дня от назначения', function () {
|
|
$result = decide([
|
|
'stage' => 'new',
|
|
'assigned_at' => CarbonImmutable::parse('2026-07-30 12:00:00'),
|
|
'next_call_at' => null,
|
|
]);
|
|
|
|
expect($result['state'])->toBe('active');
|
|
});
|
|
|
|
it('гасит новую карточку после трёх дней', function () {
|
|
$result = decide([
|
|
'stage' => 'new',
|
|
'assigned_at' => CarbonImmutable::parse('2026-07-20 12:00:00'),
|
|
'next_call_at' => null,
|
|
]);
|
|
|
|
expect($result['state'])->toBe('stopped')
|
|
->and($result['reason'])->toBe('stage_limit');
|
|
});
|
|
|
|
it('взятому в работу даёт четырнадцать дней, потому что даты созвона там нет', function () {
|
|
expect(decide([
|
|
'stage' => 'in_work',
|
|
'stage_changed_at' => CarbonImmutable::parse('2026-07-25 12:00:00'),
|
|
'next_call_at' => null,
|
|
])['state'])->toBe('active');
|
|
|
|
expect(decide([
|
|
'stage' => 'in_work',
|
|
'stage_changed_at' => CarbonImmutable::parse('2026-07-01 12:00:00'),
|
|
'next_call_at' => null,
|
|
])['state'])->toBe('stopped');
|
|
});
|
|
|
|
it('в переговорах крутит рекламу до дня созвона', function () {
|
|
expect(decide([
|
|
'stage' => 'negotiation',
|
|
'next_call_at' => CarbonImmutable::parse('2026-08-05 12:00:00'),
|
|
])['state'])->toBe('active');
|
|
});
|
|
|
|
it('после просроченного созвона даёт три дня и гасит', function () {
|
|
expect(decide([
|
|
'stage' => 'negotiation',
|
|
'next_call_at' => CarbonImmutable::parse('2026-07-30 12:00:00'),
|
|
])['state'])->toBe('active');
|
|
|
|
expect(decide([
|
|
'stage' => 'negotiation',
|
|
'next_call_at' => CarbonImmutable::parse('2026-07-20 12:00:00'),
|
|
])['state'])->toBe('stopped')
|
|
->and(decide([
|
|
'stage' => 'negotiation',
|
|
'next_call_at' => CarbonImmutable::parse('2026-07-20 12:00:00'),
|
|
])['reason'])->toBe('call_overdue');
|
|
});
|
|
|
|
it('при далёком созвоне крутит три дня, потом ставит на паузу', function () {
|
|
$far = CarbonImmutable::parse('2026-09-30 12:00:00');
|
|
|
|
expect(decide([
|
|
'stage' => 'negotiation',
|
|
'stage_changed_at' => CarbonImmutable::parse('2026-07-31 12:00:00'),
|
|
'next_call_at' => $far,
|
|
])['state'])->toBe('active');
|
|
|
|
$paused = decide([
|
|
'stage' => 'negotiation',
|
|
'stage_changed_at' => CarbonImmutable::parse('2026-07-20 12:00:00'),
|
|
'next_call_at' => $far,
|
|
]);
|
|
|
|
expect($paused['state'])->toBe('paused')
|
|
->and($paused['resume_at']->toDateString())->toBe('2026-09-27');
|
|
});
|
|
|
|
it('включает рекламу снова за три дня до далёкого созвона', function () {
|
|
expect(decide([
|
|
'stage' => 'negotiation',
|
|
'stage_changed_at' => CarbonImmutable::parse('2026-06-01 12:00:00'),
|
|
'next_call_at' => CarbonImmutable::parse('2026-08-03 12:00:00'),
|
|
], ['negotiation_far_threshold_days' => 1])['state'])->toBe('active');
|
|
});
|
|
|
|
it('недозвону даёт семь дней', function () {
|
|
expect(decide([
|
|
'stage' => 'no_answer',
|
|
'stage_changed_at' => CarbonImmutable::parse('2026-07-28 12:00:00'),
|
|
])['state'])->toBe('active');
|
|
|
|
expect(decide([
|
|
'stage' => 'no_answer',
|
|
'stage_changed_at' => CarbonImmutable::parse('2026-07-20 12:00:00'),
|
|
])['state'])->toBe('stopped');
|
|
});
|
|
|
|
it('отказу даёт три дня', function () {
|
|
expect(decide([
|
|
'stage' => 'rejected',
|
|
'stage_changed_at' => CarbonImmutable::parse('2026-07-31 12:00:00'),
|
|
])['state'])->toBe('active');
|
|
|
|
expect(decide([
|
|
'stage' => 'rejected',
|
|
'stage_changed_at' => CarbonImmutable::parse('2026-07-20 12:00:00'),
|
|
])['state'])->toBe('stopped');
|
|
});
|
|
|
|
it('регистрации и тестированию даёт тридцать дней', function () {
|
|
foreach (['registered', 'testing'] as $stage) {
|
|
expect(decide([
|
|
'stage' => $stage,
|
|
'stage_changed_at' => CarbonImmutable::parse('2026-07-20 12:00:00'),
|
|
])['state'])->toBe('active');
|
|
|
|
expect(decide([
|
|
'stage' => $stage,
|
|
'stage_changed_at' => CarbonImmutable::parse('2026-05-01 12:00:00'),
|
|
])['state'])->toBe('stopped');
|
|
}
|
|
});
|
|
|
|
it('за пополнивших баланс и пользователей мы не платим', function () {
|
|
foreach (['topped_up', 'user'] as $stage) {
|
|
$result = decide([
|
|
'stage' => $stage,
|
|
'stage_changed_at' => CarbonImmutable::parse('2026-07-31 12:00:00'),
|
|
]);
|
|
|
|
expect($result['state'])->toBe('stopped')
|
|
->and($result['reason'])->toBe('client_won');
|
|
}
|
|
});
|
|
|
|
it('слушается изменённых настроек', function () {
|
|
expect(decide([
|
|
'stage' => null,
|
|
'warmup_started_at' => CarbonImmutable::parse('2026-07-25 12:00:00'),
|
|
], ['warmup_days' => 30])['state'])->toBe('active');
|
|
});
|