Files
portal/app/tests/Feature/AdminAdvertisingSpendTest.php
T
Дмитрий f3d1d23b9c refactor(реклама показы): админ-отчёт маржи на ad_margin_percent 40% вычитанием, удалён старый AdMarkup
Задача 8b. Админ-экран «Реклама: расход и маржа» переведён со старой модели
наценка 30% делением на единую модель показов наценка 40% вычитанием, как в
CampaignImpressionCharger. yandex_cost = client_spend умножить на долю
1 минус ad_margin_percent/100. Поле ответа markup_percent переименовано в
ad_margin_percent, фронт-вью и тип обновлены.

Мёртвый класс AdMarkup и его тест удалены полностью. Колонка ad_settings
markup_percent осталась в схеме, но больше не читается. Тесты: бэк
AdminAdvertisingSpend 6/6, реклама-модуль 189/189, фронт админки 7/7.

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

120 lines
4.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
use App\Models\Tenant;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
uses(DatabaseTransactions::class);
/**
* T4a: SaaS-admin «Реклама: расход и наша маржа» — агрегат по ad_wallet_transactions
* (type=charge, channel=yandex). Наценка глобальная — ad_settings.ad_margin_percent,
* доля НАШЕЙ маржи в клиентской цене; yandex_cost = client_spend × (1 ad_margin_percent/100).
*/
function makeAdCharge(
int $tenantId,
float $amount,
?string $createdAt = null,
string $channel = 'yandex',
string $type = 'charge',
): void {
DB::table('ad_wallet_transactions')->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);
});