diff --git a/app/app/Jobs/RouteSupplierLeadJob.php b/app/app/Jobs/RouteSupplierLeadJob.php index 917222b7..4268cb2f 100644 --- a/app/app/Jobs/RouteSupplierLeadJob.php +++ b/app/app/Jobs/RouteSupplierLeadJob.php @@ -91,7 +91,20 @@ class RouteSupplierLeadJob implements ShouldQueue LeadDistributor $distributor, RegionTagResolver $tagResolver, ): void { - $lead = SupplierLead::findOrFail($this->supplierLeadId); + $lead = SupplierLead::find($this->supplierLeadId); + + // Терминальный случай: лид удалён/не существует — это НЕ транзиентная ошибка, + // повтор бессмыслен. НЕ бросаем ModelNotFoundException: иначе queue->failed() + // пишет строку в failed_webhook_jobs, а RetryFailedSupplierJobsCommand + // бесконечно перезапускает job (retry-шторм, инцидент 21-22.05.2026 — + // 25k+ записей по удалённому лиду №1). + if ($lead === null) { + Log::warning('supplier_lead.not_found_terminal', [ + 'supplier_lead_id' => $this->supplierLeadId, + ]); + + return; + } // Idempotency guard для retry-сценария ($tries = 3). // Если лид уже обработан — выходим, не создаём ghost duplicate'ы deal'ов. diff --git a/app/tests/Feature/Jobs/RouteSupplierLeadJobTest.php b/app/tests/Feature/Jobs/RouteSupplierLeadJobTest.php index 5bfc4f4b..b12af59f 100644 --- a/app/tests/Feature/Jobs/RouteSupplierLeadJobTest.php +++ b/app/tests/Feature/Jobs/RouteSupplierLeadJobTest.php @@ -48,6 +48,21 @@ function runRouteJob(int $supplierLeadId): void // `linkProjectToSupplier` helper now lives in tests/Pest.php — single source. +it('is terminal (does not throw / re-queue) when the supplier lead does not exist', function (): void { + // Регрессия retry-шторма 21-22.05.2026: RouteSupplierLeadJob для удалённого лида №1 + // бросал ModelNotFoundException -> queue->failed() писал в failed_webhook_jobs -> + // RetryFailedSupplierJobsCommand бесконечно перезапускал (25k+ записей). + // «Лид не найден» — терминальная (не транзиентная) ошибка: повтор бессмыслен. + $missingId = 999999; + expect(SupplierLead::find($missingId))->toBeNull(); + + // Не должно бросать исключение (иначе сработает failed() -> retry-цикл). + runRouteJob($missingId); + + // Никаких побочных эффектов. + expect(Deal::count())->toBe(0); +}); + it('routes 1 lead to N tenants — creates N deal copies (sharing-model)', function (): void { $supplier = SupplierProject::factory()->create([ 'platform' => 'B1',