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 { if ($installment->status !== 'paid') { throw ValidationException::withMessages([ 'status' => 'Só é possível reabrir parcelas que estão pagas.', ]); } $installment->update([ 'status' => 'pending', 'paid_value' => 0, 'payment_date' => null, ]); return $installment->fresh(['student']); } }