2026-06-17 14:07:22 +03:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
2026-06-25 05:19:41 +03:00
|
|
|
|
use App\Models\Project;
|
2026-06-17 14:07:22 +03:00
|
|
|
|
use App\Models\Tenant;
|
|
|
|
|
|
use App\Services\Billing\RunwayCalculator;
|
|
|
|
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-06-25 05:19:41 +03:00
|
|
|
|
* B1-fix (24.06.2026): runway считается от ДНЕВНОГО ЗАКАЗА активных проектов
|
|
|
|
|
|
* (sum daily_limit_target), а не от прошлого расхода. Так дашборд перестаёт врать
|
|
|
|
|
|
* на новых аккаунтах («0 дней» при полном балансе) и совпадает с витриной биллинга.
|
2026-06-17 14:07:22 +03:00
|
|
|
|
* daysLeft(tenantId, affordableLeads):
|
2026-06-25 05:19:41 +03:00
|
|
|
|
* affordable<=0 → 0; нет активных проектов (заказ=0) → null;
|
|
|
|
|
|
* иначе floor(affordable / дневной_заказ).
|
2026-06-17 14:07:22 +03:00
|
|
|
|
*/
|
|
|
|
|
|
uses(DatabaseTransactions::class);
|
|
|
|
|
|
|
2026-06-25 05:19:41 +03:00
|
|
|
|
/** Создаёт активный проект тенанта с заданным дневным лимитом. */
|
|
|
|
|
|
function makeActiveProject(int $tenantId, int $dailyLimit): void
|
2026-06-17 14:07:22 +03:00
|
|
|
|
{
|
|
|
|
|
|
DB::statement('SET app.current_tenant_id = '.$tenantId);
|
2026-06-25 05:19:41 +03:00
|
|
|
|
Project::factory()->create([
|
|
|
|
|
|
'tenant_id' => $tenantId,
|
|
|
|
|
|
'is_active' => true,
|
|
|
|
|
|
'daily_limit_target' => $dailyLimit,
|
|
|
|
|
|
]);
|
2026-06-17 14:07:22 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
test('daysLeft = 0 если affordableLeads <= 0', function () {
|
|
|
|
|
|
$tenant = Tenant::factory()->create();
|
2026-06-25 05:19:41 +03:00
|
|
|
|
makeActiveProject($tenant->id, 50);
|
2026-06-17 14:07:22 +03:00
|
|
|
|
expect(app(RunwayCalculator::class)->daysLeft($tenant->id, 0))->toBe(0);
|
|
|
|
|
|
expect(app(RunwayCalculator::class)->daysLeft($tenant->id, -5))->toBe(0);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-06-25 05:19:41 +03:00
|
|
|
|
test('daysLeft = null если нет активных проектов (нечего заказывать)', function () {
|
2026-06-17 14:07:22 +03:00
|
|
|
|
$tenant = Tenant::factory()->create();
|
|
|
|
|
|
expect(app(RunwayCalculator::class)->daysLeft($tenant->id, 100))->toBeNull();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-06-25 05:19:41 +03:00
|
|
|
|
test('daysLeft = floor(affordable / дневной заказ активных проектов)', function () {
|
2026-06-17 14:07:22 +03:00
|
|
|
|
$tenant = Tenant::factory()->create();
|
2026-06-25 05:19:41 +03:00
|
|
|
|
makeActiveProject($tenant->id, 50);
|
|
|
|
|
|
// 223 лида по карману, заказ 50/день → 4 полных дня (не «0» как раньше).
|
|
|
|
|
|
expect(app(RunwayCalculator::class)->daysLeft($tenant->id, 223))->toBe(4);
|
2026-06-17 14:07:22 +03:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-06-25 05:19:41 +03:00
|
|
|
|
test('мало денег при большом заказе → 0 дней, а не раздутое число', function () {
|
2026-06-17 14:07:22 +03:00
|
|
|
|
$tenant = Tenant::factory()->create();
|
2026-06-25 05:19:41 +03:00
|
|
|
|
makeActiveProject($tenant->id, 50);
|
|
|
|
|
|
// 1 лид по карману, заказ 50/день → 0 дней (раньше выдавало «10 из 7»).
|
|
|
|
|
|
expect(app(RunwayCalculator::class)->daysLeft($tenant->id, 1))->toBe(0);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('daysLeft суммирует лимиты нескольких активных проектов', function () {
|
|
|
|
|
|
$tenant = Tenant::factory()->create();
|
|
|
|
|
|
makeActiveProject($tenant->id, 30);
|
|
|
|
|
|
makeActiveProject($tenant->id, 20); // суммарный заказ 50/день
|
|
|
|
|
|
expect(app(RunwayCalculator::class)->daysLeft($tenant->id, 100))->toBe(2);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('daysLeft игнорирует неактивные проекты', function () {
|
|
|
|
|
|
$tenant = Tenant::factory()->create();
|
|
|
|
|
|
makeActiveProject($tenant->id, 50);
|
|
|
|
|
|
DB::statement('SET app.current_tenant_id = '.$tenant->id);
|
|
|
|
|
|
Project::factory()->create([
|
|
|
|
|
|
'tenant_id' => $tenant->id,
|
|
|
|
|
|
'is_active' => false,
|
|
|
|
|
|
'daily_limit_target' => 1000,
|
|
|
|
|
|
]);
|
|
|
|
|
|
// считаем только активный (223/50=4), неактивный 1000 не учитывается.
|
|
|
|
|
|
expect(app(RunwayCalculator::class)->daysLeft($tenant->id, 223))->toBe(4);
|
2026-06-17 14:07:22 +03:00
|
|
|
|
});
|