From 79697217aab27e7140ef032b050894ff631915cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Fri, 31 Jul 2026 11:15:42 +0300 Subject: [PATCH] =?UTF-8?q?fix(=D0=BF=D1=80=D0=BE=D0=B3=D1=80=D0=B5=D0=B2)?= =?UTF-8?q?:=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20=D1=81=D1=82=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=B8=20=D0=B2=D0=BE=D1=80=D0=BE=D0=BD=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=BD=D0=B5=20=D0=B2=D1=8B=D0=BA=D0=BB=D1=8E=D1=87=D0=B0=D1=8E?= =?UTF-8?q?=D1=82=20=D1=80=D0=B5=D0=BA=D0=BB=D0=B0=D0=BC=D1=83=20=D0=BC?= =?UTF-8?q?=D0=BE=D0=BB=D1=87=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit До правки decide на незнакомой стадии возвращал unknown_stage — реклама на такие фирмы встала бы без единого сообщения. Теперь обе новые стадии идут по правилу переговоров: крутим до созвона, потом ещё срок просрочки. Co-Authored-By: Claude Opus 5 (1M context) --- .../Services/Sales/AdAudienceScheduler.php | 9 ++-- app/tests/Unit/AdAudienceSchedulerTest.php | 42 +++++++++++++++++++ docs/observer/STATUS.md | 8 ++-- 3 files changed, 52 insertions(+), 7 deletions(-) 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-сессий.