tenant = Tenant::factory()->create(); }); function seedBillingTx(int $tenantId, string $type, float $amount, ?Carbon $at = null): void { BalanceTransaction::create([ 'tenant_id' => $tenantId, 'type' => $type, 'amount_rub' => $amount, 'amount_leads' => 0, 'description' => 'test', 'created_at' => $at ?? Carbon::now()->startOfMonth()->addDays(10), ]); } function billingJob(int $tenantId): ReportJob { return new ReportJob([ 'tenant_id' => $tenantId, 'type' => 'billing_summary', 'parameters' => [ 'format' => 'csv', 'date_from' => Carbon::now()->startOfMonth()->toDateString(), 'date_to' => Carbon::now()->endOfMonth()->toDateString(), ], ]); } test('headers: 3 колонки', function () { expect((new BillingSummaryProvider)->headers()) ->toBe(['Тип операции', 'Количество', 'Сумма (₽)']); }); test('slug = billing', function () { expect((new BillingSummaryProvider)->slug())->toBe('billing'); }); test('агрегирует balance_transactions по типу: count + сумма', function () { seedBillingTx($this->tenant->id, 'topup', 5000); seedBillingTx($this->tenant->id, 'topup', 3000); seedBillingTx($this->tenant->id, 'lead_charge', -250); $rows = (new BillingSummaryProvider)->rows(billingJob($this->tenant->id)); // ORDER BY type → 'lead_charge' < 'topup'. expect($rows)->toHaveCount(2); expect($rows[0])->toBe(['Списание за лиды', 1, '-250.00']); expect($rows[1])->toBe(['Пополнение', 2, '8000.00']); }); test('исключает транзакции вне периода', function () { seedBillingTx($this->tenant->id, 'topup', 5000); seedBillingTx($this->tenant->id, 'topup', 1000, Carbon::now()->subMonths(3)); $rows = (new BillingSummaryProvider)->rows(billingJob($this->tenant->id)); expect($rows)->toHaveCount(1); expect($rows[0])->toBe(['Пополнение', 1, '5000.00']); }); test('изолирует по tenant_id', function () { $other = Tenant::factory()->create(); seedBillingTx($other->id, 'topup', 9999); $rows = (new BillingSummaryProvider)->rows(billingJob($this->tenant->id)); expect($rows)->toBe([]); });