167 lines
7.6 KiB
PHP
167 lines
7.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use App\Services\Admin\TenantActivityFeedService;
|
|
use Carbon\CarbonImmutable;
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
uses(DatabaseTransactions::class);
|
|
|
|
function feedTenant(): array
|
|
{
|
|
$tenant = Tenant::factory()->create();
|
|
$user = User::factory()->create(['tenant_id' => $tenant->id, 'email' => 'client@example.test']);
|
|
|
|
return [(int) $tenant->id, (int) $user->id];
|
|
}
|
|
|
|
function feedOps(int $tenantId, ?int $userId, string $event, ?int $entityId, ?array $after, ?array $before, string $mskTime): void
|
|
{
|
|
DB::table('tenant_operations_log')->insert([
|
|
'tenant_id' => $tenantId,
|
|
'user_id' => $userId,
|
|
'entity_type' => 'project',
|
|
'entity_id' => $entityId,
|
|
'event' => $event,
|
|
'payload_before' => $before !== null ? json_encode($before, JSON_UNESCAPED_UNICODE) : null,
|
|
'payload_after' => $after !== null ? json_encode($after, JSON_UNESCAPED_UNICODE) : null,
|
|
'created_at' => CarbonImmutable::parse($mskTime, 'Europe/Moscow')->utc(),
|
|
]);
|
|
}
|
|
|
|
function feedActivity(int $tenantId, ?int $userId, string $event, int $dealId, string $mskTime): void
|
|
{
|
|
DB::table('activity_log')->insert([
|
|
'tenant_id' => $tenantId,
|
|
'user_id' => $userId,
|
|
'deal_id' => $dealId,
|
|
'event' => $event,
|
|
'created_at' => CarbonImmutable::parse($mskTime, 'Europe/Moscow')->utc(),
|
|
]);
|
|
}
|
|
|
|
it('берёт имя проекта из журнала, а не номер', function () {
|
|
[$tenantId, $userId] = feedTenant();
|
|
feedOps($tenantId, $userId, 'project.created', 999_001, ['name' => 'Двери'], null, '2026-08-05 10:10:00');
|
|
|
|
$items = app(TenantActivityFeedService::class)->forTenant($tenantId, 50, 0);
|
|
|
|
expect($items)->toHaveCount(1);
|
|
expect($items[0]['kind'])->toBe('project');
|
|
expect($items[0]['event'])->toBe('project.created');
|
|
expect($items[0]['subject'])->toBe('Двери');
|
|
expect($items[0]['actor'])->toBe('client@example.test');
|
|
});
|
|
|
|
it('берёт имя удалённого проекта из снимка «до»', function () {
|
|
[$tenantId, $userId] = feedTenant();
|
|
feedOps($tenantId, $userId, 'project.deleted', 999_002, null, ['name' => 'Окна ЕКБ'], '2026-08-05 11:00:00');
|
|
|
|
$items = app(TenantActivityFeedService::class)->forTenant($tenantId, 50, 0);
|
|
|
|
expect($items[0]['subject'])->toBe('Окна ЕКБ');
|
|
});
|
|
|
|
it('считает, сколько проектов задето групповым действием', function () {
|
|
[$tenantId, $userId] = feedTenant();
|
|
feedOps($tenantId, $userId, 'project.bulk_pause', null, ['ids' => [1, 2, 3]], null, '2026-08-05 12:00:00');
|
|
|
|
$items = app(TenantActivityFeedService::class)->forTenant($tenantId, 50, 0);
|
|
|
|
expect($items[0]['bulk_count'])->toBe(3);
|
|
});
|
|
|
|
it('пускает ручную работу со сделками и НЕ пускает системную', function () {
|
|
[$tenantId, $userId] = feedTenant();
|
|
feedActivity($tenantId, $userId, 'deal.status_changed', 555, '2026-08-05 13:00:00');
|
|
feedActivity($tenantId, null, 'deal.created', 556, '2026-08-05 13:05:00');
|
|
|
|
$items = app(TenantActivityFeedService::class)->forTenant($tenantId, 50, 0);
|
|
|
|
expect($items)->toHaveCount(1);
|
|
expect($items[0]['event'])->toBe('deal.status_changed');
|
|
expect($items[0]['subject'])->toBe('555');
|
|
});
|
|
|
|
it('пускает пополнение и НЕ пускает списания за лид и правку баланса админом', function () {
|
|
[$tenantId, $userId] = feedTenant();
|
|
|
|
DB::table('balance_transactions')->insert([
|
|
['tenant_id' => $tenantId, 'user_id' => $userId, 'type' => 'topup',
|
|
'amount_rub' => '1000.00', 'created_at' => CarbonImmutable::parse('2026-08-05 14:00:00', 'Europe/Moscow')->utc()],
|
|
['tenant_id' => $tenantId, 'user_id' => null, 'type' => 'lead_charge',
|
|
'amount_rub' => '-20.00', 'created_at' => CarbonImmutable::parse('2026-08-05 14:05:00', 'Europe/Moscow')->utc()],
|
|
['tenant_id' => $tenantId, 'user_id' => null, 'type' => 'manual_adjustment',
|
|
'amount_rub' => '500.00', 'created_at' => CarbonImmutable::parse('2026-08-05 14:10:00', 'Europe/Moscow')->utc()],
|
|
]);
|
|
|
|
$items = app(TenantActivityFeedService::class)->forTenant($tenantId, 50, 0);
|
|
|
|
expect($items)->toHaveCount(1);
|
|
expect($items[0]['kind'])->toBe('money');
|
|
expect($items[0]['event'])->toBe('balance.topup');
|
|
expect($items[0]['amount_rub'])->toBe('1000.00');
|
|
});
|
|
|
|
it('отдаёт свежее первым и умеет постранично', function () {
|
|
[$tenantId, $userId] = feedTenant();
|
|
feedOps($tenantId, $userId, 'project.created', 999_010, ['name' => 'Первый'], null, '2026-08-01 10:00:00');
|
|
feedOps($tenantId, $userId, 'project.created', 999_011, ['name' => 'Второй'], null, '2026-08-02 10:00:00');
|
|
feedOps($tenantId, $userId, 'project.created', 999_012, ['name' => 'Третий'], null, '2026-08-03 10:00:00');
|
|
|
|
$service = app(TenantActivityFeedService::class);
|
|
|
|
$first = $service->forTenant($tenantId, 2, 0);
|
|
expect(array_column($first, 'subject'))->toBe(['Третий', 'Второй']);
|
|
|
|
$second = $service->forTenant($tenantId, 2, 2);
|
|
expect(array_column($second, 'subject'))->toBe(['Первый']);
|
|
});
|
|
|
|
it('считает дела за период тем же отбором, что и лента', function () {
|
|
[$tenantId, $userId] = feedTenant();
|
|
feedOps($tenantId, $userId, 'project.created', 999_020, ['name' => 'Свежий'], null, '2026-08-05 10:00:00');
|
|
feedOps($tenantId, $userId, 'project.created', 999_021, ['name' => 'Старый'], null, '2026-05-01 10:00:00');
|
|
feedActivity($tenantId, null, 'deal.created', 777, '2026-08-05 10:30:00');
|
|
|
|
$since = CarbonImmutable::parse('2026-07-09 00:00:00', 'Europe/Moscow')->utc();
|
|
|
|
expect(app(TenantActivityFeedService::class)->countSince($tenantId, $since))->toBe(1);
|
|
});
|
|
|
|
it('карточка клиента отдаёт блок «жив ли» и ленту без системного шума', function () {
|
|
$tenant = Tenant::factory()->create(['subdomain' => 'testalive']);
|
|
$user = User::factory()->create(['tenant_id' => $tenant->id, 'email' => 'client@example.test']);
|
|
|
|
feedOps((int) $tenant->id, (int) $user->id, 'project.created', 999_100, ['name' => 'Двери'], null, '2026-08-05 10:10:00');
|
|
feedActivity((int) $tenant->id, null, 'deal.created', 888, '2026-08-05 11:00:00');
|
|
|
|
$resp = $this->getJson('/api/admin/tenants/testalive');
|
|
|
|
$resp->assertOk()
|
|
->assertJsonPath('aliveness.state', 'no_data')
|
|
->assertJsonCount(1, 'activity.items')
|
|
->assertJsonPath('activity.items.0.event', 'project.created')
|
|
->assertJsonPath('activity.items.0.subject', 'Двери')
|
|
->assertJsonPath('activity.has_more', false);
|
|
});
|
|
|
|
it('отдаёт следующую страницу ленты отдельной ручкой', function () {
|
|
$tenant = Tenant::factory()->create(['subdomain' => 'testpage']);
|
|
$user = User::factory()->create(['tenant_id' => $tenant->id]);
|
|
|
|
feedOps((int) $tenant->id, (int) $user->id, 'project.created', 999_200, ['name' => 'Первый'], null, '2026-08-01 10:00:00');
|
|
feedOps((int) $tenant->id, (int) $user->id, 'project.created', 999_201, ['name' => 'Второй'], null, '2026-08-02 10:00:00');
|
|
|
|
$resp = $this->getJson('/api/admin/tenants/testpage/activity?offset=1&limit=1');
|
|
|
|
$resp->assertOk()
|
|
->assertJsonCount(1, 'items')
|
|
->assertJsonPath('items.0.subject', 'Первый')
|
|
->assertJsonPath('has_more', false);
|
|
});
|