create([ 'platform' => $platform, 'signal_type' => 'call', 'unique_key' => $key, 'subject_code' => null, 'current_limit' => 7, 'sync_status' => $status, 'last_synced_at' => $syncedAt, ]); } function linkPivot(Project $project, SupplierProject $sp): void { DB::table('project_supplier_links')->insertOrIgnore([ 'project_id' => $project->id, 'supplier_project_id' => $sp->id, 'platform' => $sp->platform, 'subject_code' => null, ]); } it('reports ok when the order exists in the pivot and legacy columns are empty', function (): void { $project = Project::factory()->create([ 'signal_type' => 'call', 'signal_identifier' => '79990000001', 'supplier_b1_project_id' => null, 'supplier_b2_project_id' => null, 'supplier_b3_project_id' => null, ]); DB::table('project_supplier_links')->where('project_id', $project->id)->delete(); foreach (['B1', 'B2', 'B3'] as $platform) { linkPivot($project, spOk($platform, '79990000001')); } // Состояние, до которого доехал прод: колонки пустые, связки есть. expect($project->fresh()->supplier_b1_project_id)->toBeNull() ->and(DB::table('project_supplier_links')->where('project_id', $project->id)->count())->toBe(3); expect($project->fresh()->aggregateSyncStatus())->toBe('ok'); }); it('reports pending when a pivot-linked order is still pending', function (): void { $project = Project::factory()->create([ 'signal_type' => 'call', 'signal_identifier' => '79001112233', 'supplier_b1_project_id' => null, 'supplier_b2_project_id' => null, 'supplier_b3_project_id' => null, ]); DB::table('project_supplier_links')->where('project_id', $project->id)->delete(); linkPivot($project, spOk('B1', '79001112233')); linkPivot($project, spOk('B2', '79001112233', 'pending', null)); expect($project->fresh()->aggregateSyncStatus())->toBe('pending'); }); it('reports failed when any pivot-linked order failed', function (): void { $project = Project::factory()->create([ 'signal_type' => 'call', 'signal_identifier' => '79002223344', 'supplier_b1_project_id' => null, 'supplier_b2_project_id' => null, 'supplier_b3_project_id' => null, ]); DB::table('project_supplier_links')->where('project_id', $project->id)->delete(); linkPivot($project, spOk('B1', '79002223344')); linkPivot($project, spOk('B2', '79002223344', 'failed')); expect($project->fresh()->aggregateSyncStatus())->toBe('failed'); }); it('still reports pending when there is no order at all', function (): void { $project = Project::factory()->create([ 'signal_type' => 'call', 'signal_identifier' => '79003334455', 'supplier_b1_project_id' => null, 'supplier_b2_project_id' => null, 'supplier_b3_project_id' => null, ]); DB::table('project_supplier_links')->where('project_id', $project->id)->delete(); expect($project->fresh()->aggregateSyncStatus())->toBe('pending'); }); it('still reports ok for legacy projects that only have the old columns filled', function (): void { $sp = spOk('B1', '79004445566'); $project = Project::factory()->create([ 'signal_type' => 'call', 'signal_identifier' => '79004445566', 'supplier_b1_project_id' => $sp->id, ]); DB::table('project_supplier_links')->where('project_id', $project->id)->delete(); expect($project->fresh()->aggregateSyncStatus())->toBe('ok'); }); it('lists supplier links from the pivot when legacy columns are empty', function (): void { $project = Project::factory()->create([ 'signal_type' => 'call', 'signal_identifier' => '79005556677', 'supplier_b1_project_id' => null, 'supplier_b2_project_id' => null, 'supplier_b3_project_id' => null, ]); DB::table('project_supplier_links')->where('project_id', $project->id)->delete(); foreach (['B1', 'B2', 'B3'] as $platform) { linkPivot($project, spOk($platform, '79005556677')); } $links = $project->fresh()->getSupplierLinks(); expect($links)->toHaveCount(3) ->and(collect($links)->pluck('platform')->sort()->values()->all())->toBe(['b1', 'b2', 'b3']) ->and(collect($links)->pluck('sync_status')->unique()->all())->toBe(['ok']); }); it('takes the earliest sync time across pivot-linked orders', function (): void { $project = Project::factory()->create([ 'signal_type' => 'call', 'signal_identifier' => '79006667788', 'supplier_b1_project_id' => null, 'supplier_b2_project_id' => null, 'supplier_b3_project_id' => null, ]); DB::table('project_supplier_links')->where('project_id', $project->id)->delete(); linkPivot($project, spOk('B1', '79006667788', 'ok', '2026-07-28 15:05:00')); linkPivot($project, spOk('B2', '79006667788', 'ok', '2026-07-26 15:05:00')); expect((string) $project->fresh()->aggregateLastSyncedAt())->toStartWith('2026-07-26'); }); it('does not double-count an order linked both by pivot and by a legacy column', function (): void { $sp = spOk('B1', '79007778899'); $project = Project::factory()->create([ 'signal_type' => 'call', 'signal_identifier' => '79007778899', 'supplier_b1_project_id' => $sp->id, ]); DB::table('project_supplier_links')->where('project_id', $project->id)->delete(); linkPivot($project, $sp); expect($project->fresh()->getSupplierLinks())->toHaveCount(1); }); it('shows the project as collecting leads over the API when only the pivot is filled', function (): void { $tenant = Tenant::factory()->create(); $user = User::factory()->create(['tenant_id' => $tenant->id]); $project = Project::factory()->create([ 'tenant_id' => $tenant->id, 'signal_type' => 'call', 'signal_identifier' => '79008889900', 'is_active' => true, 'preflight_blocked_at' => null, 'supplier_b1_project_id' => null, 'supplier_b2_project_id' => null, 'supplier_b3_project_id' => null, ]); DB::table('project_supplier_links')->where('project_id', $project->id)->delete(); foreach (['B1', 'B2', 'B3'] as $platform) { linkPivot($project, spOk($platform, '79008889900')); } $response = $this->actingAs($user)->getJson('/api/projects'); $row = collect($response->json('data'))->firstWhere('id', $project->id); expect($row['sync_status'])->toBe('ok'); });