parcelas geradas -> faturamento * da unidade. Cobre o pedido de "ver se o faturamento da unidade está sendo * cobrado corretamente" usando o fluxo real (StudentContractService), não * inserindo parcela direto no banco como os outros testes de TBR fazem. */ class StudentBillingRevenueTest extends TestCase { use RefreshDatabase; private Unit $unit; private StudentContractService $contractService; private TbrCalculationService $tbrService; protected function setUp(): void { parent::setUp(); $this->unit = $this->makeUnit(); $this->contractService = new StudentContractService(); $this->tbrService = new TbrCalculationService(); } private function makeUnit(): Unit { $country = Country::create(['name' => 'Brasil', 'code' => 'BR']); $state = State::create(['name' => 'Paraná', 'code' => 'PR', 'country_id' => $country->id]); $city = City::create(['name' => 'Maringá', 'country_id' => $country->id, 'state_id' => $state->id]); return Unit::create([ 'fantasy_name' => 'Unidade Teste', 'social_reason' => 'Unidade Teste LTDA', 'cnpj' => '00000000000191', 'street' => 'Rua Teste', 'neighborhood' => 'Centro', 'postal_code' => '87000000', 'city_id' => $city->id, 'state_id' => $state->id, 'email' => 'unidade@teste.com', 'name_responsible' => 'Responsável Teste', ]); } /** * Regra: O faturamento de um mês soma as parcelas de aluno com vencimento * naquele mês — incluindo a entrada, quando cai no mesmo mês, e mesmo entre * alunos/contratos diferentes. * * Cenário: Aluno A com pacote em 3x de R$ 100 (venc. dia 10) e entrada de * R$ 500 vencendo no mesmo mês da 1ª parcela; Aluno B com matrícula de R$ 150 * vencendo no mesmo mês. * * Espera: Faturamento de 08/2026 = 100 (1ª parcela do pacote) + 500 (entrada) + 150 (matrícula) = 750. * Faturamento de 09/2026 = 100 (2ª parcela do pacote, sozinha). */ public function test_faturamento_do_mes_soma_parcelas_de_contratos_diferentes_incluindo_entrada(): void { $alunoA = Student::create(['name' => 'Aluno A', 'unit_id' => $this->unit->id, 'status' => 'active']); $this->contractService->create([ 'student_id' => $alunoA->id, 'unit_id' => $this->unit->id, 'recurring_day' => 10, 'down_payment_value' => 500.00, 'down_payment_date' => '2026-08-10', 'package_value' => 300.00, 'package_installments' => 3, 'package_due_date' => '2026-08-10', ]); $alunoB = Student::create(['name' => 'Aluno B', 'unit_id' => $this->unit->id, 'status' => 'active']); $this->contractService->create([ 'student_id' => $alunoB->id, 'unit_id' => $this->unit->id, 'tax_register' => 150.00, 'installments' => 1, 'enrollment_due_date' => '2026-08-20', ]); $revenueAgosto = $this->tbrService->resolveRevenue($this->unit->id, 2026, 8); $revenueSetembro = $this->tbrService->resolveRevenue($this->unit->id, 2026, 9); $this->assertSame(750.0, $revenueAgosto); $this->assertSame(100.0, $revenueSetembro); } /** * Regra: A entrada com vencimento num mês diferente das parcelas normais * entra no faturamento do MÊS EM QUE ELA VENCE, não no mês da 1ª parcela. */ public function test_entrada_com_vencimento_em_mes_diferente_entra_no_faturamento_do_seu_proprio_mes(): void { $aluno = Student::create(['name' => 'Aluno Teste', 'unit_id' => $this->unit->id, 'status' => 'active']); $this->contractService->create([ 'student_id' => $aluno->id, 'unit_id' => $this->unit->id, 'recurring_day' => 10, 'down_payment_value' => 500.00, 'down_payment_date' => '2026-07-25', // entrada assinada antes, vencendo em julho 'package_value' => 300.00, 'package_installments' => 3, 'package_due_date' => '2026-08-10', // parcelas normais começam em agosto ]); $this->assertSame(500.0, $this->tbrService->resolveRevenue($this->unit->id, 2026, 7)); $this->assertSame(100.0, $this->tbrService->resolveRevenue($this->unit->id, 2026, 8)); } /** * Regra: O dashboard financeiro da unidade (UnitFinancialDashboardService) * e o faturamento usado pelo cálculo de royalties (TbrCalculationService) * precisam bater — inclusive contas que a franqueadora vinculou à unidade * (origin=manual_unit) sem cobrança Asaas nenhuma. Antes dessa correção o * dashboard ignorava essa fonte; o cliente reclamava do número não bater. */ public function test_dashboard_da_unidade_soma_contas_vinculadas_pela_franqueadora_mesmo_sem_asaas(): void { FranchiseeAccountReceive::create([ 'unit_id' => $this->unit->id, 'origin' => 'manual_unit', 'history' => 'Conta vinculada pela franqueadora', 'value' => 1000, 'due_date' => now()->toDateString(), 'status' => 'pending', ]); $tbrRevenueBetween = $this->tbrService->resolveRevenueBetween( [$this->unit->id], now()->startOfMonth(), now()->endOfMonth(), ); $dashboard = (new UnitFinancialDashboardService())->overview($this->unit->id); $this->assertSame(1000.0, $tbrRevenueBetween); $this->assertSame(1000.0, $dashboard['receivable_pending']); $this->assertSame(1, $dashboard['receivable_pending_count']); } /** * Regra: Conta vinculada pela franqueadora paga dentro do mês entra em * "recebido no mês"; vencida (não paga) entra em "vencido". */ public function test_dashboard_conta_recebido_e_vencido_das_contas_vinculadas_pela_franqueadora(): void { FranchiseeAccountReceive::create([ 'unit_id' => $this->unit->id, 'origin' => 'manual_unit', 'history' => 'Paga no mês', 'value' => 200, 'paid_value' => 200, 'due_date' => now()->startOfMonth()->toDateString(), 'payment_date' => now()->toDateString(), 'status' => 'paid', ]); FranchiseeAccountReceive::create([ 'unit_id' => $this->unit->id, 'origin' => 'manual_unit', 'history' => 'Vencida', 'value' => 300, 'due_date' => now()->subDays(5)->toDateString(), 'status' => 'pending', ]); $dashboard = (new UnitFinancialDashboardService())->overview($this->unit->id); $this->assertSame(200.0, $dashboard['received_month']); $this->assertSame(300.0, $dashboard['overdue_receivable']); } }