| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 |
- <?php
- namespace Tests\Unit;
- use App\Models\City;
- use App\Models\Country;
- use App\Models\State;
- use App\Models\Student;
- use App\Models\StudentContractInstallment;
- use App\Models\Unit;
- use App\Services\StudentContractService;
- use App\Services\UnitReceivableService;
- use Illuminate\Foundation\Testing\RefreshDatabase;
- use Illuminate\Validation\ValidationException;
- use Tests\TestCase;
- /**
- * Contas a Receber da unidade (Decisão de modelagem A, documentada em
- * UnitReceivableService): as parcelas de contrato de aluno JÁ SÃO a conta a
- * receber — não existe tabela duplicada nem registro do lado do "responsável
- * financeiro". Aqui validamos que a listagem e a baixa manual funcionam em
- * cima dessa mesma tabela (student_contract_installments).
- */
- class UnitReceivableServiceTest extends TestCase
- {
- use RefreshDatabase;
- private Unit $unit;
- private UnitReceivableService $service;
- private StudentContractService $contractService;
- protected function setUp(): void
- {
- parent::setUp();
- $this->unit = $this->makeUnit();
- $this->service = new UnitReceivableService();
- $this->contractService = new StudentContractService();
- }
- private function makeUnit(string $name = 'Unidade Teste', string $cnpj = '00000000000191'): 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' => $name, 'social_reason' => $name.' LTDA', 'cnpj' => $cnpj,
- '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',
- ]);
- }
- private function makeInstallmentViaContract(int $unitId, array $overrides = []): StudentContractInstallment
- {
- $student = Student::create(['name' => 'Aluno Teste', 'unit_id' => $unitId, 'status' => 'active']);
- $contract = $this->contractService->create(array_merge([
- 'student_id' => $student->id,
- 'unit_id' => $unitId,
- 'package_value' => 300.00,
- 'package_installments' => 3,
- 'package_due_date' => '2026-08-10',
- ], $overrides));
- return StudentContractInstallment::where('student_contract_id', $contract->id)
- ->orderBy('installment_number')->first();
- }
- /**
- * Regra: A listagem de "contas a receber" da unidade é exatamente a tabela
- * de parcelas de aluno, filtrada por unidade, ordenada por vencimento.
- */
- public function test_list_retorna_as_parcelas_da_unidade_ordenadas_por_vencimento(): void
- {
- $this->makeInstallmentViaContract($this->unit->id);
- $outraUnidade = $this->makeUnit('Outra Unidade', '00000000000272');
- $this->makeInstallmentViaContract($outraUnidade->id);
- $result = $this->service->list($this->unit->id);
- $this->assertCount(3, $result); // as 3 parcelas do pacote da unidade de teste
- $this->assertTrue($result->every(fn ($i) => $i->unit_id === $this->unit->id));
- }
- public function test_list_filtra_por_status_e_por_tipo(): void
- {
- $installment = $this->makeInstallmentViaContract($this->unit->id);
- $installment->update(['status' => 'paid', 'paid_value' => 100, 'payment_date' => '2026-08-10']);
- $pending = $this->service->list($this->unit->id, ['status' => 'pending']);
- $paid = $this->service->list($this->unit->id, ['status' => 'paid']);
- $this->assertCount(2, $pending);
- $this->assertCount(1, $paid);
- $byType = $this->service->list($this->unit->id, ['type' => 'package']);
- $this->assertCount(3, $byType);
- }
- /**
- * Regra: A baixa manual, sem informar paid_value, cobra o valor da parcela
- * menos desconto mais multa.
- */
- public function test_settle_calcula_paid_value_padrao_a_partir_de_desconto_e_multa(): void
- {
- $installment = $this->makeInstallmentViaContract($this->unit->id);
- $settled = $this->service->settle($installment, ['discount' => 10, 'fine' => 5]);
- $this->assertSame('paid', $settled->status);
- $this->assertEquals(95.00, $settled->paid_value); // 100 - 10 + 5
- }
- public function test_settle_bloqueia_parcela_ja_paga(): void
- {
- $installment = $this->makeInstallmentViaContract($this->unit->id);
- $installment->update(['status' => 'paid', 'paid_value' => 100, 'payment_date' => '2026-08-10']);
- $this->expectException(ValidationException::class);
- $this->expectExceptionMessage('já está paga');
- $this->service->settle($installment, []);
- }
- public function test_settle_bloqueia_parcela_cancelada(): void
- {
- $installment = $this->makeInstallmentViaContract($this->unit->id);
- $installment->update(['status' => 'cancelled']);
- $this->expectException(ValidationException::class);
- $this->expectExceptionMessage('cancelada');
- $this->service->settle($installment, []);
- }
- /**
- * Regra: Uma parcela paga nunca reabre — a baixa manual é definitiva
- * (estorno de verdade não é suportado por este fluxo).
- */
- public function test_reopen_sempre_bloqueia(): void
- {
- $installment = $this->makeInstallmentViaContract($this->unit->id);
- $installment->update(['status' => 'paid', 'paid_value' => 100, 'payment_date' => '2026-08-10']);
- $this->expectException(ValidationException::class);
- $this->service->reopen($installment);
- }
- public function test_find_for_unit_nao_retorna_parcela_de_outra_unidade(): void
- {
- $installment = $this->makeInstallmentViaContract($this->unit->id);
- $outraUnidade = $this->makeUnit('Outra Unidade', '00000000000272');
- $this->assertNull($this->service->findForUnit($installment->id, $outraUnidade->id));
- $this->assertNotNull($this->service->findForUnit($installment->id, $this->unit->id));
- }
- }
|