UnitReceivableServiceTest.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. <?php
  2. namespace Tests\Unit;
  3. use App\Models\City;
  4. use App\Models\Country;
  5. use App\Models\State;
  6. use App\Models\Student;
  7. use App\Models\StudentContractInstallment;
  8. use App\Models\Unit;
  9. use App\Services\StudentContractService;
  10. use App\Services\UnitReceivableService;
  11. use Illuminate\Foundation\Testing\RefreshDatabase;
  12. use Illuminate\Validation\ValidationException;
  13. use Tests\TestCase;
  14. /**
  15. * Contas a Receber da unidade (Decisão de modelagem A, documentada em
  16. * UnitReceivableService): as parcelas de contrato de aluno JÁ SÃO a conta a
  17. * receber — não existe tabela duplicada nem registro do lado do "responsável
  18. * financeiro". Aqui validamos que a listagem e a baixa manual funcionam em
  19. * cima dessa mesma tabela (student_contract_installments).
  20. */
  21. class UnitReceivableServiceTest extends TestCase
  22. {
  23. use RefreshDatabase;
  24. private Unit $unit;
  25. private UnitReceivableService $service;
  26. private StudentContractService $contractService;
  27. protected function setUp(): void
  28. {
  29. parent::setUp();
  30. $this->unit = $this->makeUnit();
  31. $this->service = new UnitReceivableService();
  32. $this->contractService = new StudentContractService();
  33. }
  34. private function makeUnit(string $name = 'Unidade Teste', string $cnpj = '00000000000191'): Unit
  35. {
  36. $country = Country::create(['name' => 'Brasil', 'code' => 'BR']);
  37. $state = State::create(['name' => 'Paraná', 'code' => 'PR', 'country_id' => $country->id]);
  38. $city = City::create(['name' => 'Maringá', 'country_id' => $country->id, 'state_id' => $state->id]);
  39. return Unit::create([
  40. 'fantasy_name' => $name, 'social_reason' => $name.' LTDA', 'cnpj' => $cnpj,
  41. 'street' => 'Rua Teste', 'neighborhood' => 'Centro', 'postal_code' => '87000000',
  42. 'city_id' => $city->id, 'state_id' => $state->id,
  43. 'email' => 'unidade@teste.com', 'name_responsible' => 'Responsável Teste',
  44. ]);
  45. }
  46. private function makeInstallmentViaContract(int $unitId, array $overrides = []): StudentContractInstallment
  47. {
  48. $student = Student::create(['name' => 'Aluno Teste', 'unit_id' => $unitId, 'status' => 'active']);
  49. $contract = $this->contractService->create(array_merge([
  50. 'student_id' => $student->id,
  51. 'unit_id' => $unitId,
  52. 'package_value' => 300.00,
  53. 'package_installments' => 3,
  54. 'package_due_date' => '2026-08-10',
  55. ], $overrides));
  56. return StudentContractInstallment::where('student_contract_id', $contract->id)
  57. ->orderBy('installment_number')->first();
  58. }
  59. /**
  60. * Regra: A listagem de "contas a receber" da unidade é exatamente a tabela
  61. * de parcelas de aluno, filtrada por unidade, ordenada por vencimento.
  62. */
  63. public function test_list_retorna_as_parcelas_da_unidade_ordenadas_por_vencimento(): void
  64. {
  65. $this->makeInstallmentViaContract($this->unit->id);
  66. $outraUnidade = $this->makeUnit('Outra Unidade', '00000000000272');
  67. $this->makeInstallmentViaContract($outraUnidade->id);
  68. $result = $this->service->list($this->unit->id);
  69. $this->assertCount(3, $result); // as 3 parcelas do pacote da unidade de teste
  70. $this->assertTrue($result->every(fn ($i) => $i->unit_id === $this->unit->id));
  71. }
  72. public function test_list_filtra_por_status_e_por_tipo(): void
  73. {
  74. $installment = $this->makeInstallmentViaContract($this->unit->id);
  75. $installment->update(['status' => 'paid', 'paid_value' => 100, 'payment_date' => '2026-08-10']);
  76. $pending = $this->service->list($this->unit->id, ['status' => 'pending']);
  77. $paid = $this->service->list($this->unit->id, ['status' => 'paid']);
  78. $this->assertCount(2, $pending);
  79. $this->assertCount(1, $paid);
  80. $byType = $this->service->list($this->unit->id, ['type' => 'package']);
  81. $this->assertCount(3, $byType);
  82. }
  83. /**
  84. * Regra: A baixa manual, sem informar paid_value, cobra o valor da parcela
  85. * menos desconto mais multa.
  86. */
  87. public function test_settle_calcula_paid_value_padrao_a_partir_de_desconto_e_multa(): void
  88. {
  89. $installment = $this->makeInstallmentViaContract($this->unit->id);
  90. $settled = $this->service->settle($installment, ['discount' => 10, 'fine' => 5]);
  91. $this->assertSame('paid', $settled->status);
  92. $this->assertEquals(95.00, $settled->paid_value); // 100 - 10 + 5
  93. }
  94. public function test_settle_bloqueia_parcela_ja_paga(): void
  95. {
  96. $installment = $this->makeInstallmentViaContract($this->unit->id);
  97. $installment->update(['status' => 'paid', 'paid_value' => 100, 'payment_date' => '2026-08-10']);
  98. $this->expectException(ValidationException::class);
  99. $this->expectExceptionMessage('já está paga');
  100. $this->service->settle($installment, []);
  101. }
  102. public function test_settle_bloqueia_parcela_cancelada(): void
  103. {
  104. $installment = $this->makeInstallmentViaContract($this->unit->id);
  105. $installment->update(['status' => 'cancelled']);
  106. $this->expectException(ValidationException::class);
  107. $this->expectExceptionMessage('cancelada');
  108. $this->service->settle($installment, []);
  109. }
  110. /**
  111. * Regra: Uma parcela paga nunca reabre — a baixa manual é definitiva
  112. * (estorno de verdade não é suportado por este fluxo).
  113. */
  114. public function test_reopen_sempre_bloqueia(): void
  115. {
  116. $installment = $this->makeInstallmentViaContract($this->unit->id);
  117. $installment->update(['status' => 'paid', 'paid_value' => 100, 'payment_date' => '2026-08-10']);
  118. $this->expectException(ValidationException::class);
  119. $this->service->reopen($installment);
  120. }
  121. public function test_find_for_unit_nao_retorna_parcela_de_outra_unidade(): void
  122. {
  123. $installment = $this->makeInstallmentViaContract($this->unit->id);
  124. $outraUnidade = $this->makeUnit('Outra Unidade', '00000000000272');
  125. $this->assertNull($this->service->findForUnit($installment->id, $outraUnidade->id));
  126. $this->assertNotNull($this->service->findForUnit($installment->id, $this->unit->id));
  127. }
  128. }