StudentBillingRevenueTest.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. <?php
  2. namespace Tests\Unit\Financeiro;
  3. use App\Models\City;
  4. use App\Models\Country;
  5. use App\Models\FranchiseeAccountReceive;
  6. use App\Models\State;
  7. use App\Models\Student;
  8. use App\Models\Unit;
  9. use App\Services\StudentContractService;
  10. use App\Services\TbrCalculationService;
  11. use App\Services\UnitFinancialDashboardService;
  12. use Illuminate\Foundation\Testing\RefreshDatabase;
  13. use Tests\TestCase;
  14. /**
  15. * Ponta a ponta: contrato de aluno assinado -> parcelas geradas -> faturamento
  16. * da unidade. Cobre o pedido de "ver se o faturamento da unidade está sendo
  17. * cobrado corretamente" usando o fluxo real (StudentContractService), não
  18. * inserindo parcela direto no banco como os outros testes de TBR fazem.
  19. */
  20. class StudentBillingRevenueTest extends TestCase
  21. {
  22. use RefreshDatabase;
  23. private Unit $unit;
  24. private StudentContractService $contractService;
  25. private TbrCalculationService $tbrService;
  26. protected function setUp(): void
  27. {
  28. parent::setUp();
  29. $this->unit = $this->makeUnit();
  30. $this->contractService = new StudentContractService();
  31. $this->tbrService = new TbrCalculationService();
  32. }
  33. private function makeUnit(): Unit
  34. {
  35. $country = Country::create(['name' => 'Brasil', 'code' => 'BR']);
  36. $state = State::create(['name' => 'Paraná', 'code' => 'PR', 'country_id' => $country->id]);
  37. $city = City::create(['name' => 'Maringá', 'country_id' => $country->id, 'state_id' => $state->id]);
  38. return Unit::create([
  39. 'fantasy_name' => 'Unidade Teste', 'social_reason' => 'Unidade Teste LTDA', 'cnpj' => '00000000000191',
  40. 'street' => 'Rua Teste', 'neighborhood' => 'Centro', 'postal_code' => '87000000',
  41. 'city_id' => $city->id, 'state_id' => $state->id,
  42. 'email' => 'unidade@teste.com', 'name_responsible' => 'Responsável Teste',
  43. ]);
  44. }
  45. /**
  46. * Regra: O faturamento de um mês soma as parcelas de aluno com vencimento
  47. * naquele mês — incluindo a entrada, quando cai no mesmo mês, e mesmo entre
  48. * alunos/contratos diferentes.
  49. *
  50. * Cenário: Aluno A com pacote em 3x de R$ 100 (venc. dia 10) e entrada de
  51. * R$ 500 vencendo no mesmo mês da 1ª parcela; Aluno B com matrícula de R$ 150
  52. * vencendo no mesmo mês.
  53. *
  54. * Espera: Faturamento de 08/2026 = 100 (1ª parcela do pacote) + 500 (entrada) + 150 (matrícula) = 750.
  55. * Faturamento de 09/2026 = 100 (2ª parcela do pacote, sozinha).
  56. */
  57. public function test_faturamento_do_mes_soma_parcelas_de_contratos_diferentes_incluindo_entrada(): void
  58. {
  59. $alunoA = Student::create(['name' => 'Aluno A', 'unit_id' => $this->unit->id, 'status' => 'active']);
  60. $this->contractService->create([
  61. 'student_id' => $alunoA->id,
  62. 'unit_id' => $this->unit->id,
  63. 'recurring_day' => 10,
  64. 'down_payment_value' => 500.00,
  65. 'down_payment_date' => '2026-08-10',
  66. 'package_value' => 300.00,
  67. 'package_installments' => 3,
  68. 'package_due_date' => '2026-08-10',
  69. ]);
  70. $alunoB = Student::create(['name' => 'Aluno B', 'unit_id' => $this->unit->id, 'status' => 'active']);
  71. $this->contractService->create([
  72. 'student_id' => $alunoB->id,
  73. 'unit_id' => $this->unit->id,
  74. 'tax_register' => 150.00,
  75. 'installments' => 1,
  76. 'enrollment_due_date' => '2026-08-20',
  77. ]);
  78. $revenueAgosto = $this->tbrService->resolveRevenue($this->unit->id, 2026, 8);
  79. $revenueSetembro = $this->tbrService->resolveRevenue($this->unit->id, 2026, 9);
  80. $this->assertSame(750.0, $revenueAgosto);
  81. $this->assertSame(100.0, $revenueSetembro);
  82. }
  83. /**
  84. * Regra: A entrada com vencimento num mês diferente das parcelas normais
  85. * entra no faturamento do MÊS EM QUE ELA VENCE, não no mês da 1ª parcela.
  86. */
  87. public function test_entrada_com_vencimento_em_mes_diferente_entra_no_faturamento_do_seu_proprio_mes(): void
  88. {
  89. $aluno = Student::create(['name' => 'Aluno Teste', 'unit_id' => $this->unit->id, 'status' => 'active']);
  90. $this->contractService->create([
  91. 'student_id' => $aluno->id,
  92. 'unit_id' => $this->unit->id,
  93. 'recurring_day' => 10,
  94. 'down_payment_value' => 500.00,
  95. 'down_payment_date' => '2026-07-25', // entrada assinada antes, vencendo em julho
  96. 'package_value' => 300.00,
  97. 'package_installments' => 3,
  98. 'package_due_date' => '2026-08-10', // parcelas normais começam em agosto
  99. ]);
  100. $this->assertSame(500.0, $this->tbrService->resolveRevenue($this->unit->id, 2026, 7));
  101. $this->assertSame(100.0, $this->tbrService->resolveRevenue($this->unit->id, 2026, 8));
  102. }
  103. /**
  104. * Regra: O dashboard financeiro da unidade (UnitFinancialDashboardService)
  105. * e o faturamento usado pelo cálculo de royalties (TbrCalculationService)
  106. * precisam bater — inclusive contas que a franqueadora vinculou à unidade
  107. * (origin=manual_unit) sem cobrança Asaas nenhuma. Antes dessa correção o
  108. * dashboard ignorava essa fonte; o cliente reclamava do número não bater.
  109. */
  110. public function test_dashboard_da_unidade_soma_contas_vinculadas_pela_franqueadora_mesmo_sem_asaas(): void
  111. {
  112. FranchiseeAccountReceive::create([
  113. 'unit_id' => $this->unit->id,
  114. 'origin' => 'manual_unit',
  115. 'history' => 'Conta vinculada pela franqueadora',
  116. 'value' => 1000,
  117. 'due_date' => now()->toDateString(),
  118. 'status' => 'pending',
  119. ]);
  120. $tbrRevenueBetween = $this->tbrService->resolveRevenueBetween(
  121. [$this->unit->id],
  122. now()->startOfMonth(),
  123. now()->endOfMonth(),
  124. );
  125. $dashboard = (new UnitFinancialDashboardService())->overview($this->unit->id);
  126. $this->assertSame(1000.0, $tbrRevenueBetween);
  127. $this->assertSame(1000.0, $dashboard['receivable_pending']);
  128. $this->assertSame(1, $dashboard['receivable_pending_count']);
  129. }
  130. /**
  131. * Regra: Conta vinculada pela franqueadora paga dentro do mês entra em
  132. * "recebido no mês"; vencida (não paga) entra em "vencido".
  133. */
  134. public function test_dashboard_conta_recebido_e_vencido_das_contas_vinculadas_pela_franqueadora(): void
  135. {
  136. FranchiseeAccountReceive::create([
  137. 'unit_id' => $this->unit->id,
  138. 'origin' => 'manual_unit',
  139. 'history' => 'Paga no mês',
  140. 'value' => 200,
  141. 'paid_value' => 200,
  142. 'due_date' => now()->startOfMonth()->toDateString(),
  143. 'payment_date' => now()->toDateString(),
  144. 'status' => 'paid',
  145. ]);
  146. FranchiseeAccountReceive::create([
  147. 'unit_id' => $this->unit->id,
  148. 'origin' => 'manual_unit',
  149. 'history' => 'Vencida',
  150. 'value' => 300,
  151. 'due_date' => now()->subDays(5)->toDateString(),
  152. 'status' => 'pending',
  153. ]);
  154. $dashboard = (new UnitFinancialDashboardService())->overview($this->unit->id);
  155. $this->assertSame(200.0, $dashboard['received_month']);
  156. $this->assertSame(300.0, $dashboard['overdue_receivable']);
  157. }
  158. }