tenant = Tenant::factory()->create(); $this->project = Project::factory()->create(['tenant_id' => $this->tenant->id]); }); function seedSourceDeal(int $tenantId, int $projectId, array $overrides = []): void { DB::table('deals')->insert(array_merge([ 'tenant_id' => $tenantId, 'project_id' => $projectId, 'phone' => '+7999'.random_int(1000000, 9999999), 'status' => 'new', 'received_at' => Carbon::now()->startOfMonth()->addDays(10), 'created_at' => Carbon::now(), 'updated_at' => Carbon::now(), ], $overrides)); } function sourcesJob(int $tenantId): ReportJob { return new ReportJob([ 'tenant_id' => $tenantId, 'type' => 'sources_summary', 'parameters' => [ 'format' => 'csv', 'date_from' => Carbon::now()->startOfMonth()->toDateString(), 'date_to' => Carbon::now()->endOfMonth()->toDateString(), ], ]); } test('headers: 4 колонки', function () { expect((new SourcesSummaryProvider)->headers()) ->toBe(['Источник', 'Всего сделок', 'Оплачено', 'Конверсия (%)']); }); test('slug = sources', function () { expect((new SourcesSummaryProvider)->slug())->toBe('sources'); }); test('агрегирует сделки по utm_source', function () { seedSourceDeal($this->tenant->id, $this->project->id, ['utm_source' => 'yandex', 'status' => 'won']); seedSourceDeal($this->tenant->id, $this->project->id, ['utm_source' => 'yandex', 'status' => 'new']); seedSourceDeal($this->tenant->id, $this->project->id, ['utm_source' => 'vk', 'status' => 'won']); $rows = (new SourcesSummaryProvider)->rows(sourcesJob($this->tenant->id)); expect($rows)->toHaveCount(2); // ORDER BY COUNT(*) DESC → yandex (2) первый. expect($rows[0])->toBe(['yandex', 2, 1, 50.0]); expect($rows[1])->toBe(['vk', 1, 1, 100.0]); }); test('сделки без utm_source → «Прямые / без метки»', function () { seedSourceDeal($this->tenant->id, $this->project->id, ['utm_source' => null]); $rows = (new SourcesSummaryProvider)->rows(sourcesJob($this->tenant->id)); expect($rows)->toHaveCount(1); expect($rows[0][0])->toBe('Прямые / без метки'); }); test('исключает soft-deleted и тестовые сделки', function () { seedSourceDeal($this->tenant->id, $this->project->id, ['utm_source' => 'yandex']); seedSourceDeal($this->tenant->id, $this->project->id, ['utm_source' => 'yandex', 'deleted_at' => Carbon::now()]); seedSourceDeal($this->tenant->id, $this->project->id, ['utm_source' => 'yandex', 'is_test' => true]); $rows = (new SourcesSummaryProvider)->rows(sourcesJob($this->tenant->id)); expect($rows)->toHaveCount(1); expect($rows[0][1])->toBe(1); }); test('изолирует по tenant_id', function () { $other = Tenant::factory()->create(); $otherProject = Project::factory()->create(['tenant_id' => $other->id]); seedSourceDeal($other->id, $otherProject->id, ['utm_source' => 'yandex']); $rows = (new SourcesSummaryProvider)->rows(sourcesJob($this->tenant->id)); expect($rows)->toBe([]); });