ClassService.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. <?php
  2. namespace App\Services;
  3. use App\Models\SchoolClass;
  4. use App\Models\ClassAttendance;
  5. use App\Models\Student;
  6. use Illuminate\Database\Eloquent\Collection;
  7. use Illuminate\Support\Facades\DB;
  8. class ClassService
  9. {
  10. /**
  11. * Aulas da unidade ativa, com nome do pacote e contadores
  12. * (total de alunos do pacote e quantos foram marcados presentes).
  13. */
  14. public function getAllByUnit(int $unitId): Collection
  15. {
  16. return SchoolClass::with('packageUnit')
  17. ->withCount([
  18. 'attendances as present_count' => fn ($q) => $q->where('in_class', true),
  19. ])
  20. ->where('unit_id', $unitId)
  21. ->orderBy('date_time_start')
  22. ->get()
  23. ->each(function (SchoolClass $class) {
  24. $class->students_count = $this->activeStudentsQuery($class->class_package_unit_id, $class->unit_id)->count();
  25. });
  26. }
  27. public function findByIdForUnit(int $id, int $unitId): SchoolClass
  28. {
  29. return SchoolClass::with('packageUnit')
  30. ->where('unit_id', $unitId)
  31. ->findOrFail($id);
  32. }
  33. public function create(array $data): SchoolClass
  34. {
  35. return SchoolClass::create($data)->load('packageUnit');
  36. }
  37. public function update(int $id, int $unitId, array $data): SchoolClass
  38. {
  39. $model = $this->findByIdForUnit($id, $unitId);
  40. $model->update($data);
  41. return $model->fresh('packageUnit');
  42. }
  43. public function delete(int $id, int $unitId): bool
  44. {
  45. $model = $this->findByIdForUnit($id, $unitId);
  46. return (bool) $model->delete();
  47. }
  48. /**
  49. * Lista os alunos com contrato ATIVO no pacote vinculado à aula,
  50. * já mesclando a presença registrada (se houver) para aquela aula.
  51. */
  52. public function getStudentsForClass(int $classId, int $unitId): array
  53. {
  54. $class = $this->findByIdForUnit($classId, $unitId);
  55. $students = $this->activeStudentsQuery($class->class_package_unit_id, $class->unit_id)
  56. ->orderBy('students.name')
  57. ->get();
  58. // Presenças já registradas nesta aula, indexadas por aluno
  59. $attendances = ClassAttendance::where('class_id', $classId)
  60. ->get()
  61. ->keyBy('student_id');
  62. // Total de aulas presenciadas por aluno (em qualquer aula)
  63. $attendedCounts = ClassAttendance::select('student_id', DB::raw('COUNT(*) as total'))
  64. ->whereIn('student_id', $students->pluck('id'))
  65. ->where('in_class', true)
  66. ->groupBy('student_id')
  67. ->pluck('total', 'student_id');
  68. return $students->map(function (Student $student) use ($attendances, $attendedCounts) {
  69. $attendance = $attendances->get($student->id);
  70. return [
  71. 'student_id' => $student->id,
  72. 'name' => $student->name,
  73. 'photo_url' => $student->photo_url,
  74. 'classes_count' => (int) ($attendedCounts[$student->id] ?? 0),
  75. 'in_class' => (bool) ($attendance?->in_class ?? false),
  76. 'justified' => (bool) ($attendance?->justified ?? false),
  77. 'notes' => $attendance?->notes,
  78. ];
  79. })->all();
  80. }
  81. /**
  82. * Salva a presença da aula em lote (upsert por aluno).
  83. *
  84. * @param array<int, array{student_id:int, in_class:bool, justified?:bool, notes?:string|null}> $items
  85. */
  86. public function saveAttendance(int $classId, int $unitId, array $items): void
  87. {
  88. $class = $this->findByIdForUnit($classId, $unitId);
  89. $allowedStudentIds = $this->activeStudentsQuery($class->class_package_unit_id, $unitId)
  90. ->whereIn('students.id', collect($items)->pluck('student_id'))
  91. ->pluck('students.id');
  92. abort_unless($allowedStudentIds->count() === count($items), 422, 'A lista contém alunos que não pertencem a esta aula.');
  93. DB::transaction(function () use ($classId, $items) {
  94. foreach ($items as $item) {
  95. $inClass = (bool) ($item['in_class'] ?? false);
  96. // Justificativa só vale para faltas; presença nunca fica justificada.
  97. $justified = $inClass ? false : (bool) ($item['justified'] ?? false);
  98. ClassAttendance::updateOrCreate(
  99. [
  100. 'class_id' => $classId,
  101. 'student_id' => $item['student_id'],
  102. ],
  103. [
  104. 'in_class' => $inClass,
  105. 'justified' => $justified,
  106. 'notes' => $justified ? ($item['notes'] ?? null) : null,
  107. ],
  108. );
  109. }
  110. });
  111. }
  112. /**
  113. * Query base: alunos com contrato ativo no pacote informado (e na unidade).
  114. */
  115. private function activeStudentsQuery(?int $classPackageUnitId, int $unitId)
  116. {
  117. return Student::query()
  118. ->whereHas('contracts', function ($q) use ($classPackageUnitId, $unitId) {
  119. $q->where('status', 'active')
  120. ->where('unit_id', $unitId);
  121. if ($classPackageUnitId) {
  122. $q->where('class_package_unit_id', $classPackageUnitId);
  123. }
  124. });
  125. }
  126. }