get(), // которое садится на старую дорогую версию (садится на запись с меньшим id). beforeEach(function () { Queue::fake(); DB::statement("SELECT set_config('app.current_tenant_id', '0', true)"); // СТАРАЯ дорогая версия — создаём ПЕРВОЙ (ниже id → наивный sortBy('tier_no') садится на неё). PricingTier::query()->create([ 'tier_no' => 1, 'leads_in_tier' => 100, 'price_per_lead_kopecks' => 50000, // 500₽/лид — устаревшая версия 'is_active' => true, 'effective_from' => '2020-01-01', ]); // НОВАЯ действующая версия — свежий effective_from. PricingTier::query()->create([ 'tier_no' => 1, 'leads_in_tier' => 100, 'price_per_lead_kopecks' => 5000, // 50₽/лид — действующая версия 'is_active' => true, 'effective_from' => now(), ]); }); it('topup unblocks project using the CURRENT cheap tariff, not the stale expensive one', function () { $tenant = Tenant::factory()->withRequisites()->create(['balance_rub' => '0.00']); $project = Project::factory()->for($tenant)->create([ 'is_active' => true, 'daily_limit_target' => 30, 'preflight_blocked_at' => now(), ]); // 2000₽: по действующей 50₽/лид = 40 лидов >= 30 → блок снять. // (по устаревшей 500₽/лид = 4 лида < 30 → остался бы заблокирован — это и есть баг.) app(BillingTopupService::class)->topup($tenant->id, '2000.00', null); expect($project->fresh()->preflight_blocked_at)->toBeNull(); Queue::assertPushed(SyncSupplierProjectJob::class); }); it('creates project (no 409) when limit is affordable at the CURRENT tariff', function () { // 2000₽: действующая 50₽ = 40 лидов >= 30 → создать (по устаревшей 500₽ = 4 < 30 → 409 = БАГ). $tenant = Tenant::factory()->withRequisites()->create(['balance_rub' => '2000.00']); $user = User::factory()->create(['tenant_id' => $tenant->id]); $this->actingAs($user)->postJson('/api/projects', [ 'name' => 'По действующей цене', 'signal_type' => 'site', 'signal_identifier' => 'current-tariff.ru', 'daily_limit_target' => 30, 'regions' => [], 'delivery_days_mask' => 127, ])->assertStatus(201); expect(Project::where('signal_identifier', 'current-tariff.ru')->exists())->toBeTrue(); }); it('applies bulk limit increase affordable at the CURRENT tariff', function () { // 2000₽: действующая 50₽ = 40 лидов. Два проекта по 15 → сумма 30 ≤ 40 → применить. // (по устаревшей 500₽ = 4 < 30 → оба снялись бы = БАГ.) $tenant = Tenant::factory()->withRequisites()->create(['balance_rub' => '2000.00']); $user = User::factory()->create(['tenant_id' => $tenant->id]); $p1 = Project::factory()->for($tenant)->create(['is_active' => true, 'daily_limit_target' => 5, 'delivered_today' => 0]); $p2 = Project::factory()->for($tenant)->create(['is_active' => true, 'daily_limit_target' => 5, 'delivered_today' => 0]); $this->actingAs($user)->postJson('/api/projects/bulk', [ 'action' => 'update_limit', 'ids' => [$p1->id, $p2->id], 'replace' => 15, ])->assertOk()->assertJsonPath('updated', 2); expect($p1->fresh()->daily_limit_target)->toBe(15); expect($p2->fresh()->daily_limit_target)->toBe(15); }); it('does NOT freeze tenant affordable at the CURRENT tariff during the sweep', function () { // 2000₽: действующая 50₽ = 40 лидов >= 30 → НЕ замораживать. // (по устаревшей 500₽ = 4 < 30 → заморозил бы = БАГ.) $tenant = Tenant::factory()->create(['balance_rub' => '2000.00', 'frozen_by_balance_at' => null]); Project::factory()->for($tenant)->create(['is_active' => true, 'daily_limit_target' => 30]); (new BalancePreflightSweepJob)->handle(); expect($tenant->fresh()->frozen_by_balance_at)->toBeNull(); }); it('reminder email shows capacity at the CURRENT tariff, not the stale one', function () { Mail::fake(); // Замороженный клиент в окне reminder (25ч). 2000₽, лимит 100 → нужно 100. // действующая 50₽ = 40 лидов вместимость; устаревшая 500₽ = 4 (это попало бы в письмо = БАГ). $tenant = Tenant::factory()->create([ 'balance_rub' => '2000.00', 'frozen_by_balance_at' => now()->subHours(25), ]); Project::factory()->for($tenant)->create(['is_active' => true, 'daily_limit_target' => 100]); (new BalanceFrozenReminderJob)->handle(); Mail::assertQueued(BalanceFrozenReminderMail::class, fn ($mail) => $mail->result->capacityLeads === 40); });