delete(); PricingTier::create([ 'tier_no' => 1, 'leads_in_tier' => 50, 'price_per_lead_kopecks' => 12000, 'is_active' => true, 'effective_from' => now()->subDay()->toDateString(), ]); }); it('migrates balance_leads to balance_rub at tier 1 price', function () { $tenant = Tenant::factory()->create([ 'balance_leads' => 5, 'balance_rub' => '100.00', ]); $this->artisan('billing:migrate-leads-to-rub')->assertOk(); $tenant->refresh(); expect($tenant->balance_leads)->toBe(0); expect((string) $tenant->balance_rub)->toBe('700.00'); // 100 + 5×120 = 700 $tx = BalanceTransaction::where('tenant_id', $tenant->id) ->where('type', BalanceTransaction::TYPE_MIGRATION) ->first(); expect($tx)->not->toBeNull(); expect((int) $tx->amount_leads)->toBe(-5); expect((string) $tx->amount_rub)->toBe('600.00'); expect((string) $tx->balance_rub_after)->toBe('700.00'); }); it('is idempotent — second run is no-op', function () { $tenant = Tenant::factory()->create([ 'balance_leads' => 5, 'balance_rub' => '100.00', ]); $this->artisan('billing:migrate-leads-to-rub')->assertOk(); $balanceAfterFirst = $tenant->fresh()->balance_rub; $this->artisan('billing:migrate-leads-to-rub')->assertOk(); expect($tenant->fresh()->balance_rub)->toBe($balanceAfterFirst); expect(BalanceTransaction::where('type', BalanceTransaction::TYPE_MIGRATION)->count())->toBe(1); }); it('skips tenants with balance_leads = 0', function () { Tenant::factory()->create([ 'balance_leads' => 0, 'balance_rub' => '500.00', ]); $this->artisan('billing:migrate-leads-to-rub')->assertOk(); expect(BalanceTransaction::where('type', BalanceTransaction::TYPE_MIGRATION)->count())->toBe(0); }); it('aborts if no active tier 1 configured', function () { PricingTier::query()->update(['is_active' => false]); Tenant::factory()->create(['balance_leads' => 5]); $this->artisan('billing:migrate-leads-to-rub')->assertFailed(); });