8696b5e27f
- D2: requiredLeadsForTomorrow переведён на полный лимит, откат share-aware R-19 [решение владельца] - B/D3: пополнение снимает клиентскую заморозку И блоки проектов вместе, политика всё-или-ничего - F/J/D6: вечерний пересчёт 18:00 снимает блоки проектов у незаморожённых; общий ProjectBlockReleaseService; иерархия заморозка > блок - fix: balance_freeze_log INSERT переведён на главное соединение — межсессионный self-lock с FOR UPDATE топапа [найден живым прогоном, pg_blocking подтвердил; в тестах маскировался SharesSupplierPdo] - spec + plan в docs/superpowers 138/138 биллинг-тестов GREEN. Pint чисто. Живьём B+F подтверждены на докалке. На прод НЕ катилось. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
74 lines
3.8 KiB
PHP
74 lines
3.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\Project;
|
|
use App\Models\Tenant;
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Str;
|
|
use Tests\Concerns\SharesSupplierPdo;
|
|
|
|
uses(DatabaseTransactions::class, SharesSupplierPdo::class);
|
|
|
|
it('sums daily_limit_target of active projects for required leads', function () {
|
|
$tenant = Tenant::factory()->create(['balance_rub' => '1000.00']);
|
|
Project::factory()->for($tenant)->create(['is_active' => true, 'daily_limit_target' => 10]);
|
|
Project::factory()->for($tenant)->create(['is_active' => true, 'daily_limit_target' => 15]);
|
|
Project::factory()->for($tenant)->create(['is_active' => false, 'daily_limit_target' => 100]); // не считается
|
|
|
|
expect($tenant->requiredLeadsForTomorrow())->toBe(25);
|
|
});
|
|
|
|
it('casts frozen_by_balance_at to datetime', function () {
|
|
$tenant = Tenant::factory()->create(['frozen_by_balance_at' => now()]);
|
|
expect($tenant->frozen_by_balance_at)->toBeInstanceOf(Carbon::class);
|
|
});
|
|
|
|
it('casts project preflight_blocked_at to datetime', function () {
|
|
$project = Project::factory()->create(['preflight_blocked_at' => now()]);
|
|
expect($project->preflight_blocked_at)->toBeInstanceOf(Carbon::class);
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// D2 (23.06.2026, спека balance-lock-unify-FJ): единый расчёт required по ПОЛНОМУ
|
|
// лимиту активных проектов — откат share-aware (R-19) по решению владельца.
|
|
// requiredLeadsForTomorrow = SUM(daily_limit_target where is_active=true), без
|
|
// учёта доли группового заказа поставщика. Один расчёт во всех точках (заморозка,
|
|
// разморозка, снятие проектного блока), чтобы замки вели себя предсказуемо.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
it('returns full daily_limit_target for a single call project', function () {
|
|
$phone = '7919'.Str::random(7); // unique per run to dodge any pre-existing leakage
|
|
$tenant = Tenant::factory()->create(['balance_rub' => '1000.00']);
|
|
Project::factory()->for($tenant)->asCallSignal($phone)->create([
|
|
'is_active' => true, 'daily_limit_target' => 10,
|
|
]);
|
|
expect($tenant->fresh()->requiredLeadsForTomorrow())->toBe(10);
|
|
});
|
|
|
|
it('returns full daily_limit_target even when 3 tenants share one call source (full-limit policy)', function () {
|
|
$sharedPhone = '7929'.Str::random(7); // unique shared identifier per run
|
|
// 3 tenants, same call source, each daily_limit_target=10.
|
|
// Единый расчёт по полному лимиту (D2): доля группы НЕ учитывается → 10, а не 4.
|
|
$tenants = [];
|
|
foreach (range(1, 3) as $i) {
|
|
$t = Tenant::factory()->create(['balance_rub' => '1000.00']);
|
|
Project::factory()->for($t)->asCallSignal($sharedPhone)->create([
|
|
'is_active' => true,
|
|
'daily_limit_target' => 10,
|
|
]);
|
|
$tenants[] = $t;
|
|
}
|
|
expect($tenants[0]->fresh()->requiredLeadsForTomorrow())->toBe(10);
|
|
});
|
|
|
|
it('sums full limit for legacy webhook projects (signal_type=null)', function () {
|
|
// Regression-protection for existing TenantPreflightTest behavior.
|
|
// Webhook-only projects don't participate in supplier sharing — their full limit counts.
|
|
$tenant = Tenant::factory()->create(['balance_rub' => '1000.00']);
|
|
Project::factory()->for($tenant)->create(['is_active' => true, 'daily_limit_target' => 10]);
|
|
Project::factory()->for($tenant)->create(['is_active' => true, 'daily_limit_target' => 15]);
|
|
expect($tenant->fresh()->requiredLeadsForTomorrow())->toBe(25);
|
|
});
|