Forráskód Böngészése

feat(unit-receivable): service de contas a receber da unidade com baixa manual

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ebagabee 4 hete
szülő
commit
66708d99b8
1 módosított fájl, 102 hozzáadás és 0 törlés
  1. 102 0
      app/Services/UnitReceivableService.php

+ 102 - 0
app/Services/UnitReceivableService.php

@@ -0,0 +1,102 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\StudentContractInstallment;
+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']);
+    }
+
+    /**
+     * 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']);
+    }
+}