Files
portal/app/tests/Feature/Admin/AdminDashboardClientsTest.php
T
Дмитрий 1ecb965981 feat(дашборд): плитка «Клиенты» — активность + новые + спящие
6-я плитка «👥 Клиенты» со светофором (amber если есть спящие) + drill:
KPI за период (всего активных / новых / заходили / получали лиды / платили),
список новых клиентов (с датой входа/лидами/балансом) и «спящих» (активные
без входа 14+ дней или ни разу = не активировались). Клик по строке → карточка
клиента. Backend: clients() endpoint + clientsTile в summary (cross-tenant через
pgsql_admin); сигналы — users.last_login_at, deals, balance_transactions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 08:28:47 +03:00

62 lines
3.0 KiB
PHP

<?php
declare(strict_types=1);
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
uses(DatabaseTransactions::class);
beforeEach(function () {
DB::table('balance_transactions')->delete();
DB::table('users')->delete();
DB::table('tenants')->delete();
});
function seedTenant(array $over = []): int
{
return DB::table('tenants')->insertGetId(array_merge([
'subdomain' => 'acme'.uniqid(), 'organization_name' => 'Acme', 'contact_email' => 'a@acme.ru',
'status' => 'active', 'is_trial' => false, 'balance_rub' => 100, 'balance_leads' => 0,
'chargeback_unrecovered_rub' => 0, 'delivered_in_month' => 0,
'created_at' => now(), 'updated_at' => now(),
], $over));
}
it('GET /api/admin/dashboard/clients возвращает KPI + новых + спящих', function () {
// новый клиент, заходил недавно
$t1 = seedTenant(['organization_name' => 'Новый Актив', 'created_at' => now()->subDays(2)]);
DB::table('users')->insert(['tenant_id' => $t1, 'email' => 'u1@x.ru',
'password_hash' => bcrypt('x'), 'last_login_at' => now()->subDay(), 'created_at' => now(), 'updated_at' => now()]);
// новый клиент, НИ РАЗУ не заходил → спящий
$t2 = seedTenant(['organization_name' => 'Не Активировался', 'created_at' => now()->subDays(3)]);
DB::table('users')->insert(['tenant_id' => $t2, 'email' => 'u2@x.ru',
'password_hash' => bcrypt('x'), 'last_login_at' => null, 'created_at' => now(), 'updated_at' => now()]);
$res = $this->getJson('/api/admin/dashboard/clients?period=30d');
$res->assertOk();
$res->assertJsonStructure([
'kpi' => ['total_active', 'new_count', 'logged_in', 'got_leads', 'paid'],
'new_clients' => [['id', 'organization_name', 'created_at', 'last_login_at', 'delivered_in_month', 'balance_rub', 'status']],
'dormant' => [['id', 'organization_name', 'last_login_at', 'balance_rub']],
]);
expect($res->json('kpi.total_active'))->toBe(2);
expect($res->json('kpi.new_count'))->toBe(2);
expect($res->json('kpi.logged_in'))->toBe(1);
// спящий = не активировавшийся t2
expect(collect($res->json('dormant'))->pluck('organization_name'))->toContain('Не Активировался');
});
it('summary включает плитку clients со светофором', function () {
$t = seedTenant(['created_at' => now()->subDays(1)]);
DB::table('users')->insert(['tenant_id' => $t, 'email' => 'u@x.ru',
'password_hash' => bcrypt('x'), 'last_login_at' => null, 'created_at' => now(), 'updated_at' => now()]);
$res = $this->getJson('/api/admin/dashboard?period=30d');
$res->assertOk();
$res->assertJsonStructure(['clients' => ['light', 'total_active', 'new_count', 'logged_in', 'dormant']]);
// есть не активировавшийся → светофор amber
expect($res->json('clients.light'))->toBe('amber');
});