insert([ 'tenant_id' => $tenantId, 'type' => $type, 'amount_rub' => $type === 'charge' ? -$amount : $amount, 'balance_rub_after' => '0.00', 'channel' => $channel, 'related_type' => 'campaign', 'related_id' => 1, 'created_at' => $createdAt ?? now(), ]); } test('GET /api/admin/advertising/spend считает расход/маржу по тенантам (390/234/156 и 100/60/40)', function () { $a = Tenant::factory()->create(['organization_name' => 'Окна Москва']); $b = Tenant::factory()->create(['organization_name' => 'Двери СПб']); makeAdCharge($a->id, 260.00); makeAdCharge($a->id, 130.00); makeAdCharge($b->id, 100.00); $r = $this->getJson('/api/admin/advertising/spend'); $r->assertStatus(200); expect($r->json('ad_margin_percent'))->toBe('40.00'); $rowA = collect($r->json('data'))->firstWhere('tenant_id', $a->id); expect($rowA['tenant_name'])->toBe('Окна Москва'); expect($rowA['client_spend_rub'])->toBe('390.00'); expect($rowA['yandex_cost_rub'])->toBe('234.00'); expect($rowA['our_margin_rub'])->toBe('156.00'); $rowB = collect($r->json('data'))->firstWhere('tenant_id', $b->id); expect($rowB['client_spend_rub'])->toBe('100.00'); expect($rowB['yandex_cost_rub'])->toBe('60.00'); expect($rowB['our_margin_rub'])->toBe('40.00'); expect($r->json('totals.client_spend_rub'))->toBe('490.00'); expect($r->json('totals.yandex_cost_rub'))->toBe('294.00'); expect($r->json('totals.our_margin_rub'))->toBe('196.00'); }); test('GET /api/admin/advertising/spend сортирует по client_spend_rub desc', function () { $small = Tenant::factory()->create(); $big = Tenant::factory()->create(); makeAdCharge($small->id, 50.00); makeAdCharge($big->id, 500.00); $r = $this->getJson('/api/admin/advertising/spend'); expect($r->json('data.0.tenant_id'))->toBe($big->id); expect($r->json('data.1.tenant_id'))->toBe($small->id); }); test('GET /api/admin/advertising/spend игнорирует не-charge и не-yandex строки', function () { $tenant = Tenant::factory()->create(); makeAdCharge($tenant->id, 200.00); // считается makeAdCharge($tenant->id, 999.00, null, 'sms'); // другой канал — не считается makeAdCharge($tenant->id, 999.00, null, 'yandex', 'topup'); // не charge — не считается $r = $this->getJson('/api/admin/advertising/spend'); $row = collect($r->json('data'))->firstWhere('tenant_id', $tenant->id); expect($row['client_spend_rub'])->toBe('200.00'); }); test('GET /api/admin/advertising/spend не включает тенанта без расхода', function () { Tenant::factory()->create(); // без единой ad_wallet_transactions строки $r = $this->getJson('/api/admin/advertising/spend'); expect($r->json('data'))->toBe([]); expect($r->json('totals.client_spend_rub'))->toBe('0.00'); }); test('GET /api/admin/advertising/spend period=current_month не включает прошлый месяц, period=all включает', function () { $tenant = Tenant::factory()->create(); makeAdCharge($tenant->id, 150.00); // текущий месяц makeAdCharge($tenant->id, 700.00, now()->subMonths(2)->toDateTimeString()); // 2 месяца назад $current = $this->getJson('/api/admin/advertising/spend?period=current_month'); $row = collect($current->json('data'))->firstWhere('tenant_id', $tenant->id); expect($row['client_spend_rub'])->toBe('150.00'); $all = $this->getJson('/api/admin/advertising/spend?period=all'); $rowAll = collect($all->json('data'))->firstWhere('tenant_id', $tenant->id); expect($rowAll['client_spend_rub'])->toBe('850.00'); }); test('GET /api/admin/advertising/spend — гейт: включён + нет REMOTE_USER → 403', function () { config(['admin.basic_auth_gate' => true]); config(['admin.basic_auth_allowlist' => ['admin']]); $this->getJson('/api/admin/advertising/spend')->assertStatus(403); });