| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- <?php
- namespace App\Services;
- use App\Exceptions\AsaasException;
- use App\Jobs\SyncStudentChargeJob;
- use App\Models\StudentContractInstallment;
- use App\Models\UnitAccountReceivable;
- use App\Models\UnitPaymentAccount;
- use Carbon\Carbon;
- use Illuminate\Database\Eloquent\Collection;
- use Illuminate\Validation\ValidationException;
- /**
- * Contas a Receber da unidade.
- *
- * Decisão de modelagem (A): as parcelas de contrato de aluno
- * (student_contract_installments) SÃO a Contas a Receber de alunos da unidade.
- * A baixa é manual aqui; quando houver Asaas na unidade, a baixa também chega
- * pelo webhook (ramo STU_).
- */
- class UnitReceivableService
- {
- public function list(int $unitId, array $filters = []): Collection
- {
- return StudentContractInstallment::with(['student'])
- ->where('unit_id', $unitId)
- ->when(
- isset($filters['status']),
- fn ($q) => $q->where('status', $filters['status']),
- )
- ->when(
- isset($filters['student_id']),
- fn ($q) => $q->where('student_id', $filters['student_id']),
- )
- ->when(
- isset($filters['type']),
- fn ($q) => $q->where('type', $filters['type']),
- )
- ->orderBy('due_date')
- ->orderBy('installment_number')
- ->get();
- }
- public function findForUnit(int $id, int $unitId): ?StudentContractInstallment
- {
- return StudentContractInstallment::where('id', $id)
- ->where('unit_id', $unitId)
- ->first();
- }
- /**
- * Baixa manual: marca a parcela como paga.
- */
- public function settle(StudentContractInstallment $installment, array $data): StudentContractInstallment
- {
- if ($installment->status === 'paid') {
- throw ValidationException::withMessages([
- 'status' => 'Esta parcela já está paga.',
- ]);
- }
- if ($installment->status === 'cancelled') {
- throw ValidationException::withMessages([
- 'status' => 'Esta parcela está cancelada e não pode receber baixa.',
- ]);
- }
- $discount = isset($data['discount']) ? (float) $data['discount'] : (float) $installment->discount;
- $fine = isset($data['fine']) ? (float) $data['fine'] : (float) $installment->fine;
- $paidValue = isset($data['paid_value'])
- ? (float) $data['paid_value']
- : round((float) $installment->value - $discount + $fine, 2);
- $installment->update([
- 'status' => 'paid',
- 'discount' => $discount,
- 'fine' => $fine,
- 'paid_value' => $paidValue,
- 'payment_date' => $data['payment_date'] ?? Carbon::today()->format('Y-m-d'),
- ]);
- return $installment->fresh(['student']);
- }
- /**
- * Gera a cobrança da parcela na conta Asaas da unidade (sob demanda).
- */
- public function generateCharge(StudentContractInstallment $installment): StudentContractInstallment
- {
- if ($installment->status === 'cancelled') {
- throw ValidationException::withMessages([
- 'status' => 'Parcela cancelada não pode ser cobrada.',
- ]);
- }
- if ($installment->asaas_id) {
- throw ValidationException::withMessages([
- 'asaas' => 'Esta parcela já possui cobrança gerada.',
- ]);
- }
- $account = UnitPaymentAccount::where('unit_id', $installment->unit_id)->first();
- if (!$account || empty($account->asaas_api_key)) {
- throw ValidationException::withMessages([
- 'asaas' => 'A unidade não possui chave Asaas configurada.',
- ]);
- }
- try {
- SyncStudentChargeJob::dispatchSync($installment);
- } catch (AsaasException $e) {
- throw ValidationException::withMessages([
- 'asaas' => 'Não foi possível gerar a cobrança na Asaas. Tente novamente.',
- ]);
- }
- return $installment->fresh(['student']);
- }
- /**
- * Reabre uma parcela paga (estorno da baixa manual).
- */
- public function reopen(StudentContractInstallment $installment): StudentContractInstallment
- {
- throw ValidationException::withMessages([
- 'status' => 'Uma parcela paga não pode voltar para um status anterior.',
- ]);
- }
- // ------------------------------------------------------------------
- // Recebíveis avulsos (manuais) — tabela unit_account_receivables
- // ------------------------------------------------------------------
- public function listManual(int $unitId, array $filters = []): Collection
- {
- return UnitAccountReceivable::with('student')
- ->where('unit_id', $unitId)
- ->when(
- isset($filters['status']),
- fn ($q) => $q->where('status', $filters['status']),
- )
- ->when(
- isset($filters['student_id']),
- fn ($q) => $q->where('student_id', $filters['student_id']),
- )
- ->orderBy('due_date')
- ->get();
- }
- public function findManualForUnit(int $id, int $unitId): ?UnitAccountReceivable
- {
- return UnitAccountReceivable::where('id', $id)
- ->where('unit_id', $unitId)
- ->first();
- }
- public function createManual(int $unitId, array $data): UnitAccountReceivable
- {
- return UnitAccountReceivable::create([
- 'unit_id' => $unitId,
- 'origin' => UnitAccountReceivable::ORIGIN_MANUAL,
- 'student_id' => $data['student_id'] ?? null,
- 'financial_plan_account_id' => $data['financial_plan_account_id'] ?? null,
- 'history' => $data['history'],
- 'value' => $data['value'],
- 'due_date' => $data['due_date'],
- 'obs' => $data['obs'] ?? null,
- 'paid_value' => 0,
- 'discount' => 0,
- 'fine' => 0,
- 'status' => 'pending',
- ])->load('student');
- }
- public function settleManual(UnitAccountReceivable $receivable, array $data): UnitAccountReceivable
- {
- if ($receivable->status === 'paid') {
- throw ValidationException::withMessages([
- 'status' => 'Este recebível já está pago.',
- ]);
- }
- if ($receivable->status === 'cancelled') {
- throw ValidationException::withMessages([
- 'status' => 'Este recebível está cancelado e não pode receber baixa.',
- ]);
- }
- $discount = isset($data['discount']) ? (float) $data['discount'] : (float) $receivable->discount;
- $fine = isset($data['fine']) ? (float) $data['fine'] : (float) $receivable->fine;
- $paidValue = isset($data['paid_value'])
- ? (float) $data['paid_value']
- : round((float) $receivable->value - $discount + $fine, 2);
- $receivable->update([
- 'status' => 'paid',
- 'discount' => $discount,
- 'fine' => $fine,
- 'paid_value' => $paidValue,
- 'payment_date' => $data['payment_date'] ?? Carbon::today()->format('Y-m-d'),
- ]);
- return $receivable->fresh('student');
- }
- public function reopenManual(UnitAccountReceivable $receivable): UnitAccountReceivable
- {
- throw ValidationException::withMessages([
- 'status' => 'Um recebível pago não pode voltar para um status anterior.',
- ]);
- }
- }
|