diff --git a/app/tests/Feature/Integration/SupplierLeadFlowTest.php b/app/tests/Feature/Integration/SupplierLeadFlowTest.php index c3c1995d..a0c38d5b 100644 --- a/app/tests/Feature/Integration/SupplierLeadFlowTest.php +++ b/app/tests/Feature/Integration/SupplierLeadFlowTest.php @@ -36,7 +36,7 @@ it('end-to-end: 1 webhook → 3 deal copies for 3 active tenants', function (): for ($i = 0; $i < 3; $i++) { $t = Tenant::factory()->create(['balance_leads' => 100]); $tenants->push($t); - $projects->push(Project::factory()->create([ + $project = Project::factory()->create([ 'tenant_id' => $t->id, 'supplier_b1_project_id' => $supplier->id, 'signal_type' => 'site', @@ -49,18 +49,24 @@ it('end-to-end: 1 webhook → 3 deal copies for 3 active tenants', function (): 'region_mode' => 'include', 'daily_limit_target' => 10, 'effective_daily_limit_today' => null, - ])); + ]); + $projects->push($project); + // v8.26 (Plan 1-2): LeadRouter eligibility — через pivot project_supplier_links, + // не legacy supplier_b1_project_id. Без pivot-связи проект не eligible → 0 сделок. + linkProjectToSupplier($project, $supplier); } - // 4-й tenant — paused + // 4-й tenant — paused (is_active=false). Связь в pivot есть, чтобы проверялся + // именно фильтр is_active, а не отсутствие связи. $pausedTenant = Tenant::factory()->create(['balance_leads' => 100]); - Project::factory()->create([ + $pausedProject = Project::factory()->create([ 'tenant_id' => $pausedTenant->id, 'supplier_b1_project_id' => $supplier->id, 'signal_type' => 'site', 'signal_identifier' => 'vashinvestor.ru', 'is_active' => false, ]); + linkProjectToSupplier($pausedProject, $supplier); $vid = 432176649; $response = $this->postJson('/api/webhook/supplier/test-secret-32chars-aaaaaaaaaaaaaa', [ diff --git a/app/tests/Feature/Integration/SupplierProjectsAccessTest.php b/app/tests/Feature/Integration/SupplierProjectsAccessTest.php index 4e491b29..1243bdfa 100644 --- a/app/tests/Feature/Integration/SupplierProjectsAccessTest.php +++ b/app/tests/Feature/Integration/SupplierProjectsAccessTest.php @@ -27,19 +27,23 @@ test('supplier_projects table exists with required columns', function () { } }); -test('supplier_projects has unique constraint on (platform, unique_key)', function () { +test('supplier_projects has unique constraint on (platform, unique_key, subject_code)', function () { + // v8.26 (project-migration-redesign Plan 1): per-субъект экспорт — composite unique + // расширен до (platform, unique_key, subject_code) NULLS NOT DISTINCT. Старый + // 2-колоночный индекс supplier_projects_platform_unique_key_unique заменён. $idx = DB::selectOne( "SELECT indexdef FROM pg_indexes WHERE tablename = 'supplier_projects' - AND indexname = 'supplier_projects_platform_unique_key_unique'" + AND indexname = 'supplier_projects_platform_key_subject_unique'" ); expect($idx)->not->toBeNull(); expect($idx->indexdef) ->toContain('UNIQUE') ->toContain('platform') - ->toContain('unique_key'); + ->toContain('unique_key') + ->toContain('subject_code'); }); test('supplier_projects platform check constraint allows only B1, B2, B3', function () { diff --git a/app/tests/Feature/Plan4/Schema/SchemaDeltaTest.php b/app/tests/Feature/Plan4/Schema/SchemaDeltaTest.php index 1505f325..5e02c820 100644 --- a/app/tests/Feature/Plan4/Schema/SchemaDeltaTest.php +++ b/app/tests/Feature/Plan4/Schema/SchemaDeltaTest.php @@ -59,26 +59,28 @@ it('supplier_csv_reconcile_log table exists with required columns and status CHE ]))->toThrow(QueryException::class); }); -it('schema.sql v8.25 has correct metrics — 64 base tables, 121 indexes, 40 RLS policies', function () { +it('schema.sql v8.26 has correct metrics — 65 base tables, 123 indexes, 40 RLS policies', function () { // Замена destructive `migrate:fresh` (cross-test coupling: после DROP CASCADE остальные // Feature-тесты в той же сессии видели пустую БД). Static parse `db/schema.sql` — - // источник истины метрик из spec §2.4 / db/CHANGELOG_schema.md v8.25. + // источник истины метрик из spec §2.4 / db/CHANGELOG_schema.md v8.26. // v8.21 (Sprint 4): +1 таблица import_unknown_statuses, +1 индекс, +1 RLS-политика. // v8.22 (Plan 6/C9): +1 GIN-индекс idx_projects_regions. // v8.25 (supplier-failover): +1 таблица supplier_manual_sync_queue, +2 индекса. + // v8.26 (project-migration-redesign Plans 1-3): +1 таблица project_supplier_links (M:N pivot) + // + 2 индекса (supplier_projects_platform_key_subject_unique, idx_psl_*). $schemaPath = dirname(base_path()).DIRECTORY_SEPARATOR.'db'.DIRECTORY_SEPARATOR.'schema.sql'; expect(is_file($schemaPath) && is_readable($schemaPath))->toBeTrue(); $schema = file_get_contents($schemaPath); expect($schema)->not->toBeFalse(); - // 64 base tables = все CREATE TABLE минус 12 партиций (PARTITION OF). + // 65 base tables = все CREATE TABLE минус 12 партиций (PARTITION OF). $createTables = preg_match_all('/^CREATE TABLE\b/m', $schema); $partitionOf = preg_match_all('/CREATE TABLE\s+\w+\s+PARTITION OF\b/m', $schema); $baseTables = $createTables - $partitionOf; - expect($baseTables)->toBe(64); + expect($baseTables)->toBe(65); $createIndexes = preg_match_all('/^CREATE\s+(?:UNIQUE\s+)?INDEX\b/m', $schema); - expect($createIndexes)->toBe(121); // v8.25: +2 idx_smsq_status_created, idx_smsq_project + expect($createIndexes)->toBe(123); // v8.26: +2 supplier_projects_platform_key_subject_unique, idx_psl_* $createPolicies = preg_match_all('/^CREATE\s+POLICY\b/m', $schema); expect($createPolicies)->toBe(40); diff --git a/db/schema.sql b/db/schema.sql index b192844e..3109ddd2 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -1,7 +1,8 @@ -- ============================================================================= -- schema.sql — единая схема БД для SaaS-аналога crm.bp-gr.ru («Лидерра») --- Версия: v8.25 (19.05.2026 — supplier_manual_sync_queue: SaaS-level Tier 3 очередь резерва канала миграции проектов) --- Метрики: 64 базовые таблицы (62 regular + 2 partitioned parents: deals + supplier_lead_costs) + 12 партиций / 121 индекс / 40 RLS-политик / 5 функций / 13 триггеров +-- Версия: v8.26 (20.05.2026 — project-migration-redesign Plans 1-3: supplier_projects.subject_code (per-субъект экспорт) + project_supplier_links (M:N pivot projects↔supplier_projects) + deals.subject_code + CHECK chk_deals_subject_code + seed system_settings.supplier_export_mode) +-- Метрики: 65 базовые таблицы (63 regular + 2 partitioned parents: deals + supplier_lead_costs) + 12 партиций / 123 индекса / 40 RLS-политик / 5 функций / 13 триггеров +-- Базовая версия: v8.25 (19.05.2026 — supplier_manual_sync_queue: SaaS-level Tier 3 очередь резерва канала миграции проектов) -- Базовая версия: v8.24 (18.05.2026 — supplier_leads.vid → nullable для CSV-recovered лидов (Путь 2)) -- Базовая версия: v8.20 (11.05.2026 — Plan 5 frontend projects UI: projects.archived_at TIMESTAMPTZ NULL для soft archive flow; tenants.limits JSONB NOT NULL DEFAULT '{}' для per-tenant project/user лимитов) -- Базовая версия: v8.19 (11.05.2026 — Plan 4 billing+csv+admin: tenants.delivered_in_month, lead_charges.charge_source + CHECK, supplier_leads.recovered_from_csv_at, supplier_csv_reconcile_log)