2026-08-05 04:26:27 +03:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
|
|
use App\Models\AdCampaign;
|
|
|
|
|
|
use App\Models\Tenant;
|
|
|
|
|
|
use App\Models\User;
|
2026-08-05 07:40:27 +03:00
|
|
|
|
use App\Services\Advertising\YandexMinimumSpend;
|
2026-08-05 04:26:27 +03:00
|
|
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
|
|
|
|
|
|
|
uses(DatabaseTransactions::class);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Минимум площадки — в мастере, а не на последнем шаге.
|
|
|
|
|
|
*
|
|
|
|
|
|
* 🔴 Живой отказ 30.07.2026: Директ не берёт кампанию, у которой расход за период ниже
|
|
|
|
|
|
* его минимума, и отвечает по-английски — «Budget for this period cannot be less than
|
|
|
|
|
|
* 600 rub.». Клиент видел этот текст ПОСЛЕ того, как собрал аудиторию (15 часов),
|
|
|
|
|
|
* залил пятнадцать картинок и нажал «Запустить». Понять из него, чего от него хотят,
|
|
|
|
|
|
* невозможно.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Минимум считается от длины периода — 300 ₽ за каждый календарный день. Перевести его
|
|
|
|
|
|
* в понятные клиенту величины (сколько показов и сколько рублей) можно только на сервере:
|
|
|
|
|
|
* в расчёте участвует наша наценка, а её клиенту показывать нельзя.
|
|
|
|
|
|
*
|
|
|
|
|
|
* 🔑 Формула ОДНА на портал: и запуск, и мастер спрашивают один и тот же счётчик.
|
|
|
|
|
|
* Две копии одного расчёта однажды разойдутся, и верить будет нечему.
|
|
|
|
|
|
*/
|
|
|
|
|
|
beforeEach(function () {
|
|
|
|
|
|
$this->tenant = Tenant::factory()->create();
|
|
|
|
|
|
$this->user = User::factory()->create(['tenant_id' => $this->tenant->id]);
|
|
|
|
|
|
$this->actingAs($this->user);
|
|
|
|
|
|
|
|
|
|
|
|
config(['services.yandex_direct.min_spend_limit_rub_per_day' => 300]);
|
|
|
|
|
|
config(['services.yandex_direct.spend_limit_guard_multiplier' => '1.2']);
|
|
|
|
|
|
DB::table('ad_settings')->update(['ad_margin_percent' => '40.00']);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('мастер получает минимум площадки в показах и рублях, наценку не раскрывая', function () {
|
|
|
|
|
|
$campaign = AdCampaign::create([
|
|
|
|
|
|
'tenant_id' => $this->tenant->id,
|
|
|
|
|
|
'name' => 'Черновик',
|
|
|
|
|
|
'status' => AdCampaign::STATUS_DRAFT,
|
|
|
|
|
|
'audience_days' => 10,
|
|
|
|
|
|
'mode' => AdCampaign::MODE_AUTO,
|
|
|
|
|
|
'run_days' => 1,
|
|
|
|
|
|
'client_cpm_rub' => '500.00',
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
$otvet = $this->getJson("/api/advertising/campaigns/{$campaign->id}/audience-size?frequency=5&days=10")
|
|
|
|
|
|
->assertOk();
|
|
|
|
|
|
|
|
|
|
|
|
// Срок 1 день → период сегодня…завтра = 2 календарных дня → минимум Яндексу 600 ₽.
|
|
|
|
|
|
// Яндексу уходит 60 % от 500 ₽ = 300 ₽ за тысячу; с запасом 1,2 это 0,36 ₽ за показ.
|
|
|
|
|
|
// 600 / 0,36 = 1 666,67 → 1 667 показов. Клиенту это 1667 × 500 / 1000 = 833,50 ₽.
|
|
|
|
|
|
$otvet->assertJsonPath('min_impressions', 1667)
|
|
|
|
|
|
->assertJsonPath('min_cost_rub', '833.50');
|
|
|
|
|
|
|
|
|
|
|
|
// Наценка, доля Яндекса и его ставка клиенту не видны ни в каком виде.
|
|
|
|
|
|
$telo = $otvet->getContent();
|
|
|
|
|
|
expect($telo)->not->toContain('margin')
|
|
|
|
|
|
->and($telo)->not->toContain('yandex_cpm')
|
|
|
|
|
|
->and($telo)->not->toContain('300.00');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('запуск и мастер считают минимум ОДНИМ счётчиком', function () {
|
2026-08-05 07:40:27 +03:00
|
|
|
|
$schetchik = app(YandexMinimumSpend::class);
|
2026-08-05 04:26:27 +03:00
|
|
|
|
|
|
|
|
|
|
$itog = $schetchik->minimum(1, '500.00');
|
|
|
|
|
|
|
|
|
|
|
|
expect($itog['impressions'])->toBe(1667)
|
|
|
|
|
|
->and($itog['cost_rub'])->toBe('833.50');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('длиннее срок — выше минимум: неделя стоит семь дней по 300 ₽', function () {
|
2026-08-05 07:40:27 +03:00
|
|
|
|
$schetchik = app(YandexMinimumSpend::class);
|
2026-08-05 04:26:27 +03:00
|
|
|
|
|
|
|
|
|
|
// Срок 7 дней → период сегодня…+6 = 7 календарных дней → 2100 ₽ Яндексу.
|
|
|
|
|
|
// 2100 / 0,36 = 5 833,33 → 5 834 показа → 2 917,00 ₽ клиенту.
|
|
|
|
|
|
$itog = $schetchik->minimum(7, '500.00');
|
|
|
|
|
|
|
|
|
|
|
|
expect($itog['impressions'])->toBe(5834)
|
|
|
|
|
|
->and($itog['cost_rub'])->toBe('2917.00');
|
|
|
|
|
|
});
|