diff --git a/app/app/Jobs/RouteSupplierLeadJob.php b/app/app/Jobs/RouteSupplierLeadJob.php index 1510da1a..bf4e1808 100644 --- a/app/app/Jobs/RouteSupplierLeadJob.php +++ b/app/app/Jobs/RouteSupplierLeadJob.php @@ -171,11 +171,16 @@ class RouteSupplierLeadJob implements ShouldQueue */ private function parseProjectField(string $project): array { - if (preg_match('/^(B[123])_(.+)$/', $project, $m) !== 1) { - throw new RuntimeException("Cannot parse supplier project field: '{$project}'"); + if (preg_match('/^(B[123])_(.+)$/', $project, $m) === 1) { + $platform = $m[1]; + $rest = $m[2]; + } else { + // Phase 3: проекты без B-префикса попадают в DIRECT. + // Весь project считается identifier-частью; signal_type определяется + // тем же regex'ом, что для $rest у B-префиксных. + $platform = 'DIRECT'; + $rest = $project; } - $platform = $m[1]; - $rest = $m[2]; // Домен с латинским TLD ≥2 букв (последний сегмент — только буквы), допускается // в любой позиции строки. Соответствует чистому rest и встроенному в текст домену. diff --git a/app/app/Services/LeadRouter.php b/app/app/Services/LeadRouter.php index a980fe79..0df9015d 100644 --- a/app/app/Services/LeadRouter.php +++ b/app/app/Services/LeadRouter.php @@ -47,6 +47,39 @@ class LeadRouter // МСК-aligned ISO day-of-week (reset-cron тоже 00:00 МСК). $todayBit = 1 << (Carbon::now('Europe/Moscow')->isoWeekday() - 1); + // Phase 3: для DIRECT-supplier_project — fallback на signal_type+signal_identifier + // match с Лидерра-проектами, потому что project_supplier_links для DIRECT-row'ов + // не создаются (новые DIRECT supplier_projects создаются автоматически при + // получении webhook'а без B-префикса; explicit psl-link для них не настраивается). + if ($supplierProject->platform === 'DIRECT') { + $directSql = <<<'SQL' + SELECT DISTINCT ON (projects.tenant_id) projects.* + FROM projects + WHERE projects.signal_type = ? + AND LOWER(projects.signal_identifier) = LOWER(?) + AND projects.is_active = true + AND (projects.delivery_days_mask & ?) <> 0 + AND projects.delivered_today < COALESCE(projects.effective_daily_limit_today, projects.daily_limit_target) + AND EXISTS ( + SELECT 1 FROM tenants + WHERE tenants.id = projects.tenant_id + AND (tenants.balance_leads > 0 OR tenants.balance_rub > 0) + ) + ORDER BY + projects.tenant_id, + (COALESCE(projects.effective_daily_limit_today, projects.daily_limit_target) - projects.delivered_today) DESC, + projects.created_at, + projects.id + SQL; + $directRows = DB::connection('pgsql_supplier')->select( + $directSql, + [$supplierProject->signal_type, $supplierProject->unique_key, $todayBit] + ); + + return Project::hydrate($directRows)->values(); + } + + // Existing B1/B2/B3 path — explicit project_supplier_links pivot. $sql = <<<'SQL' SELECT DISTINCT ON (projects.tenant_id) projects.* FROM projects diff --git a/docs/observer/STATUS.md b/docs/observer/STATUS.md index f4a8d3e5..4956af51 100644 --- a/docs/observer/STATUS.md +++ b/docs/observer/STATUS.md @@ -1,6 +1,6 @@ # Brain Status (auto-generated) -Last updated: 2026-05-25T14:59:04.311Z +Last updated: 2026-05-25T14:59:06.257Z | Контролёр | Состояние | Детали | |---|---|---|