fix(supplier): RouteSupplierLeadJob терминален при отсутствии лида (стоп retry-шторм)

findOrFail -> find + ранний выход при null: 'лид удалён/не существует' — терминальная, не транзиентная ошибка. Раньше ModelNotFoundException -> queue->failed() писал в failed_webhook_jobs -> RetryFailedSupplierJobsCommand бесконечно перезапускал (инцидент 21-22.05: 25k+ записей по удалённому лиду №1). +тест RED->GREEN.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-05-22 09:26:24 +03:00
parent 4c80a5823f
commit 0c9357af7a
2 changed files with 29 additions and 1 deletions
+14 -1
View File
@@ -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'ов.
@@ -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',