Ver código fonte

feat(class): lógica de presença e alunos por contrato ativo

ClassService lista alunos com contrato ativo no pacote da aula,
mescla a presença registrada e salva a chamada em lote (justificativa
apenas para faltas).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ebagabee 1 mês atrás
pai
commit
d59b3f85ee
1 arquivos alterados com 109 adições e 10 exclusões
  1. 109 10
      app/Services/ClassService.php

+ 109 - 10
app/Services/ClassService.php

@@ -3,48 +3,147 @@
 namespace App\Services;
 
 use App\Models\SchoolClass;
+use App\Models\ClassAttendance;
+use App\Models\Student;
 use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Support\Facades\DB;
 
 class ClassService
 {
-    public function getAll(): Collection
+    /**
+     * Aulas da unidade ativa, com nome do pacote e contadores
+     * (total de alunos do pacote e quantos foram marcados presentes).
+     */
+    public function getAllByUnit(int $unitId): Collection
     {
-        return SchoolClass::orderBy('created_at', 'desc')
-            ->get();
+        return SchoolClass::with('packageUnit')
+            ->withCount([
+                'attendances as present_count' => fn ($q) => $q->where('in_class', true),
+            ])
+            ->where('unit_id', $unitId)
+            ->orderBy('date_time_start')
+            ->get()
+            ->each(function (SchoolClass $class) {
+                $class->students_count = $this->activeStudentsQuery($class->class_package_unit_id, $class->unit_id)->count();
+            });
     }
 
     public function findById(int $id): ?SchoolClass
     {
-        return SchoolClass::find($id);
+        return SchoolClass::with('packageUnit')->find($id);
     }
 
     public function create(array $data): SchoolClass
     {
-        return SchoolClass::create($data);
+        return SchoolClass::create($data)->load('packageUnit');
     }
 
     public function update(int $id, array $data): ?SchoolClass
     {
-        $model = $this->findById($id);
+        $model = SchoolClass::find($id);
 
         if (!$model) {
             return null;
         }
 
         $model->update($data);
-        return $model->fresh();
+        return $model->fresh('packageUnit');
     }
 
     public function delete(int $id): bool
     {
-        $model = $this->findById($id);
+        $model = SchoolClass::find($id);
 
         if (!$model) {
             return false;
         }
 
-        return $model->delete();
+        return (bool) $model->delete();
+    }
+
+    /**
+     * Lista os alunos com contrato ATIVO no pacote vinculado à aula,
+     * já mesclando a presença registrada (se houver) para aquela aula.
+     */
+    public function getStudentsForClass(int $classId): array
+    {
+        $class = SchoolClass::find($classId);
+        if (!$class) {
+            return [];
+        }
+
+        $students = $this->activeStudentsQuery($class->class_package_unit_id, $class->unit_id)
+            ->orderBy('students.name')
+            ->get();
+
+        // Presenças já registradas nesta aula, indexadas por aluno
+        $attendances = ClassAttendance::where('class_id', $classId)
+            ->get()
+            ->keyBy('student_id');
+
+        // Total de aulas presenciadas por aluno (em qualquer aula)
+        $attendedCounts = ClassAttendance::select('student_id', DB::raw('COUNT(*) as total'))
+            ->whereIn('student_id', $students->pluck('id'))
+            ->where('in_class', true)
+            ->groupBy('student_id')
+            ->pluck('total', 'student_id');
+
+        return $students->map(function (Student $student) use ($attendances, $attendedCounts) {
+            $attendance = $attendances->get($student->id);
+
+            return [
+                'student_id'    => $student->id,
+                'name'          => $student->name,
+                'photo_url'     => $student->photo_url,
+                'classes_count' => (int) ($attendedCounts[$student->id] ?? 0),
+                'in_class'      => (bool) ($attendance?->in_class ?? false),
+                'justified'     => (bool) ($attendance?->justified ?? false),
+                'notes'         => $attendance?->notes,
+            ];
+        })->all();
     }
 
-    // Add custom business logic methods here
+    /**
+     * Salva a presença da aula em lote (upsert por aluno).
+     *
+     * @param array<int, array{student_id:int, in_class:bool, justified?:bool, notes?:string|null}> $items
+     */
+    public function saveAttendance(int $classId, array $items): void
+    {
+        DB::transaction(function () use ($classId, $items) {
+            foreach ($items as $item) {
+                $inClass   = (bool) ($item['in_class'] ?? false);
+                // Justificativa só vale para faltas; presença nunca fica justificada.
+                $justified = $inClass ? false : (bool) ($item['justified'] ?? false);
+
+                ClassAttendance::updateOrCreate(
+                    [
+                        'class_id'   => $classId,
+                        'student_id' => $item['student_id'],
+                    ],
+                    [
+                        'in_class'  => $inClass,
+                        'justified' => $justified,
+                        'notes'     => $justified ? ($item['notes'] ?? null) : null,
+                    ],
+                );
+            }
+        });
+    }
+
+    /**
+     * Query base: alunos com contrato ativo no pacote informado (e na unidade).
+     */
+    private function activeStudentsQuery(?int $classPackageUnitId, int $unitId)
+    {
+        return Student::query()
+            ->whereHas('contracts', function ($q) use ($classPackageUnitId, $unitId) {
+                $q->where('status', 'active')
+                    ->where('unit_id', $unitId);
+
+                if ($classPackageUnitId) {
+                    $q->where('class_package_unit_id', $classPackageUnitId);
+                }
+            });
+    }
 }