diff --git a/app/app/Services/Sales/AdAudienceScheduler.php b/app/app/Services/Sales/AdAudienceScheduler.php index 0b78dbc2..2e0af4cd 100644 --- a/app/app/Services/Sales/AdAudienceScheduler.php +++ b/app/app/Services/Sales/AdAudienceScheduler.php @@ -54,7 +54,10 @@ class AdAudienceScheduler return $this->stopped('client_won'); } - if ($stage === 'negotiation') { + // Три стадии с обязательной датой созвона живут по одному правилу: крутим + // до созвона, потом ещё negotiation_overdue_days, дальше стоп. Без этой + // строки новые стадии попали бы в 'unknown_stage' и реклама МОЛЧА встала бы. + if (in_array($stage, ['negotiation', 'manual_testing', 'kp_sent'], true)) { return $this->negotiation($firm, $now); } @@ -72,8 +75,8 @@ class AdAudienceScheduler } /** - * Переговоры — единственная стадия, где дата созвона обязательна, - * поэтому логика владельца («просрочена → ещё три дня → стоп») тут полная. + * Правило для стадий с обязательной датой созвона — «Переговоры», «Тестирование + * ручное» и «Выслано КП»: логика владельца («просрочена → ещё три дня → стоп»). * * @param array $firm * @return array{state: string, reason: ?string, resume_at: ?CarbonImmutable} diff --git a/app/tests/Unit/AdAudienceSchedulerTest.php b/app/tests/Unit/AdAudienceSchedulerTest.php index 89479e86..2a1d5af3 100644 --- a/app/tests/Unit/AdAudienceSchedulerTest.php +++ b/app/tests/Unit/AdAudienceSchedulerTest.php @@ -191,3 +191,45 @@ it('слушается изменённых настроек', function () { 'warmup_started_at' => CarbonImmutable::parse('2026-07-25 12:00:00'), ], ['warmup_days' => 30])['state'])->toBe('active'); }); + +it('на ручном тестировании крутит рекламу до созвона, как на переговорах', function () { + $result = decide([ + 'stage' => 'manual_testing', + 'stage_changed_at' => CarbonImmutable::parse('2026-07-31 12:00:00'), + 'next_call_at' => CarbonImmutable::parse('2026-08-03 12:00:00'), + ]); + + expect($result['state'])->toBe('active'); +}); + +it('на ручном тестировании останавливает рекламу, когда созвон просрочен больше срока', function () { + $result = decide([ + 'stage' => 'manual_testing', + 'stage_changed_at' => CarbonImmutable::parse('2026-07-20 12:00:00'), + 'next_call_at' => CarbonImmutable::parse('2026-07-25 12:00:00'), + ]); + + expect($result['state'])->toBe('stopped') + ->and($result['reason'])->toBe('call_overdue'); +}); + +it('на «выслано КП» крутит рекламу до созвона', function () { + $result = decide([ + 'stage' => 'kp_sent', + 'stage_changed_at' => CarbonImmutable::parse('2026-07-31 12:00:00'), + 'next_call_at' => CarbonImmutable::parse('2026-08-03 12:00:00'), + ]); + + expect($result['state'])->toBe('active'); +}); + +it('на «выслано КП» останавливает рекламу, когда созвон просрочен больше срока', function () { + $result = decide([ + 'stage' => 'kp_sent', + 'stage_changed_at' => CarbonImmutable::parse('2026-07-20 12:00:00'), + 'next_call_at' => CarbonImmutable::parse('2026-07-25 12:00:00'), + ]); + + expect($result['state'])->toBe('stopped') + ->and($result['reason'])->toBe('call_overdue'); +}); diff --git a/docs/observer/STATUS.md b/docs/observer/STATUS.md index f42ea937..bb8569c5 100644 --- a/docs/observer/STATUS.md +++ b/docs/observer/STATUS.md @@ -1,6 +1,6 @@ # Brain Status (auto-generated) -Last updated: 2026-07-31T08:10:36.167Z +Last updated: 2026-07-31T08:13:53.842Z | Контролёр | Состояние | Детали | |---|---|---| @@ -112,9 +112,9 @@ Episodes since last run: 542 / threshold: 10 | PID | Имя | CPU-время | Возраст | |---|---|---|---| -| 3544 | MsMpEng | 17.45ч | 0.0ч | -| 23936 | Code | 5.12ч | NaNч | -| 4 | System | 2.65ч | 12285992.2ч | +| 3544 | MsMpEng | 17.50ч | 0.0ч | +| 23936 | Code | 5.15ч | NaNч | +| 4 | System | 2.66ч | NaNч | ⚠️ Проверь, не «осиротевшие» ли это процессы от завершённых Claude-сессий.