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'); });