add grading, attendnace management

This commit is contained in:
root
2026-04-29 17:39:33 -04:00
parent 8beeed883f
commit df5266c5b5
27 changed files with 386 additions and 157 deletions
@@ -0,0 +1,226 @@
<?php
namespace App\Http\Controllers\Api\Attendance;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Models\Configuration;
use App\Models\EarlyDismissalSignature;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Symfony\Component\HttpFoundation\Response;
class EarlyDismissalsController extends BaseApiController
{
public function __construct()
{
parent::__construct();
}
/**
* GET /api/v1/attendance/early-dismissals
* Returns early dismissal records grouped by report_date, plus a signature map.
*/
public function index(Request $request): JsonResponse
{
$schoolYear = $request->query('school_year');
$semester = $request->query('semester');
$query = DB::table('parent_attendance_reports as par')
->select([
'par.id',
'par.report_date',
'par.dismiss_time',
'par.reason',
'par.status',
'par.school_year',
'par.semester',
'par.class_section_id',
's.firstname',
's.lastname',
'cs.class_section_name',
])
->join('students as s', 's.id', '=', 'par.student_id')
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'par.class_section_id')
->where('par.type', 'early_dismissal')
->orderBy('par.report_date', 'desc')
->orderByRaw("CONCAT(s.firstname, ' ', s.lastname) ASC");
if ($schoolYear) {
$query->where('par.school_year', $schoolYear);
}
if ($semester) {
$query->where('par.semester', $semester);
}
$rows = $query->get();
$groups = [];
foreach ($rows as $row) {
$date = $row->report_date ?? 'Unknown';
$groups[$date][] = [
'id' => $row->id,
'firstname' => $row->firstname,
'lastname' => $row->lastname,
'class_section_name' => $row->class_section_name,
'dismiss_time' => $row->dismiss_time,
'reason' => $row->reason,
'status' => $row->status,
'school_year' => $row->school_year,
'semester' => $row->semester,
];
}
$dates = array_keys($groups);
$signatures = EarlyDismissalSignature::query()
->whereIn('report_date', $dates)
->get(['report_date', 'filename', 'original_name']);
$signatureByDate = [];
foreach ($signatures as $sig) {
$signatureByDate[(string) $sig->report_date] = [
'filename' => $sig->filename,
'original_name' => $sig->original_name,
];
}
return $this->success([
'groups' => $groups,
'signatureByDate' => $signatureByDate,
]);
}
/**
* GET /api/v1/attendance/early-dismissals/student-options
* Returns active students with their current class section.
*/
public function studentOptions(): JsonResponse
{
$students = DB::table('students as s')
->select([
's.id',
's.firstname',
's.lastname',
'cs.class_section_name',
])
->leftJoin('student_class as sc', function ($j) {
$j->on('sc.student_id', '=', 's.id')
->where('sc.school_year', '=', Configuration::getConfigValueByKey('school_year'));
})
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
->where('s.is_active', 1)
->orderByRaw("CONCAT(s.firstname, ' ', s.lastname) ASC")
->get();
return $this->success(['students' => $students]);
}
/**
* POST /api/v1/attendance/early-dismissals
* Creates a new early dismissal record.
*/
public function store(Request $request): JsonResponse
{
$validator = Validator::make($request->all(), [
'student_id' => ['required', 'integer', 'min:1', 'exists:students,id'],
'date' => ['required', 'date_format:Y-m-d'],
'dismiss_time' => ['required', 'date_format:H:i'],
'reason' => ['nullable', 'string', 'max:2000'],
]);
if ($validator->fails()) {
return $this->respondValidationError($validator->errors()->toArray());
}
$data = $validator->validated();
$schoolYear = (string) Configuration::getConfigValueByKey('school_year');
$semester = (string) Configuration::getConfigValueByKey('semester');
$sc = DB::table('student_class')
->where('student_id', $data['student_id'])
->where('school_year', $schoolYear)
->orderBy('id')
->first();
$classSectionId = $sc?->class_section_id ?? null;
$parentId = DB::table('students')
->where('id', $data['student_id'])
->value('parent_id') ?? Auth::id();
DB::table('parent_attendance_reports')->insert([
'parent_id' => $parentId ?? Auth::id(),
'student_id' => $data['student_id'],
'class_section_id' => $classSectionId,
'report_date' => $data['date'],
'type' => 'early_dismissal',
'dismiss_time' => $data['dismiss_time'],
'reason' => $data['reason'] ?? null,
'semester' => $semester,
'school_year' => $schoolYear,
'status' => 'new',
'created_at' => now(),
'updated_at' => now(),
]);
return $this->success([], 'Early dismissal saved.', Response::HTTP_CREATED);
}
/**
* POST /api/v1/attendance/early-dismissals/signature
* Uploads (or replaces) the signature file for a report date.
*/
public function uploadSignature(Request $request): JsonResponse
{
$validator = Validator::make($request->all(), [
'report_date' => ['required', 'date_format:Y-m-d'],
'school_year' => ['nullable', 'string', 'max:16'],
'semester' => ['nullable', 'string', 'max:32'],
'signature_file' => ['required', 'file', 'mimes:jpg,jpeg,png,webp,gif,pdf', 'max:5120'],
]);
if ($validator->fails()) {
return $this->respondValidationError($validator->errors()->toArray());
}
$file = $request->file('signature_file');
$reportDate = $request->input('report_date');
$schoolYear = $request->input('school_year') ?: (string) Configuration::getConfigValueByKey('school_year');
$semester = $request->input('semester') ?: (string) Configuration::getConfigValueByKey('semester');
$dir = storage_path('uploads/early_dismissal_signatures');
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
$existing = EarlyDismissalSignature::query()
->where('report_date', $reportDate)
->where('school_year', $schoolYear)
->where('semester', $semester)
->first();
if ($existing?->filename && file_exists($dir . '/' . $existing->filename)) {
unlink($dir . '/' . $existing->filename);
}
$filename = uniqid('sig_', true) . '.' . $file->getClientOriginalExtension();
$file->move($dir, $filename);
EarlyDismissalSignature::updateOrCreate(
['report_date' => $reportDate, 'school_year' => $schoolYear, 'semester' => $semester],
[
'filename' => $filename,
'original_name' => $file->getClientOriginalName(),
'mime_type' => $file->getClientMimeType(),
'file_size' => $file->getSize(),
'uploaded_by' => Auth::id(),
]
);
return $this->success(['filename' => $filename], 'Signature uploaded.');
}
}
@@ -72,6 +72,7 @@ class GradingController extends BaseApiController
'scores' => GradingScoreResource::collection($data['scores']),
'type' => $data['type'],
'class_section_id' => $data['class_section_id'],
'class_section_name' => $data['class_section_name'],
'semester' => $data['semester'],
'scores_locked' => $data['scores_locked'],
]);
@@ -67,6 +67,7 @@ class FinalController extends BaseApiController
return response()->json([
'ok' => true,
'students' => ScoreStudentResource::collection($data['students']),
'class_section_name' => $data['classSectionName'],
'semester' => $data['semester'],
'school_year' => $data['schoolYear'],
'scores_locked' => $data['scoresLocked'],
@@ -73,6 +73,7 @@ class HomeworkController extends BaseApiController
'ok' => true,
'students' => ScoreStudentResource::collection($data['students']),
'headers' => $data['headers'],
'class_section_name' => $data['classSectionName'],
'semester' => $data['semester'],
'school_year' => $data['schoolYear'],
'scores_locked' => $data['scoresLocked'],
@@ -67,6 +67,7 @@ class MidtermController extends BaseApiController
return response()->json([
'ok' => true,
'students' => ScoreStudentResource::collection($data['students']),
'class_section_name' => $data['classSectionName'],
'semester' => $data['semester'],
'school_year' => $data['schoolYear'],
'scores_locked' => $data['scoresLocked'],
@@ -71,6 +71,7 @@ class ParticipationController extends BaseApiController
return response()->json([
'ok' => true,
'students' => ScoreStudentResource::collection($data['students']),
'class_section_name' => $data['classSectionName'],
'semester' => $data['semester'],
'school_year' => $data['schoolYear'],
'scores_locked' => $data['scoresLocked'],
@@ -73,6 +73,7 @@ class ProjectController extends BaseApiController
'ok' => true,
'students' => ScoreStudentResource::collection($data['students']),
'headers' => $data['headers'],
'class_section_name' => $data['classSectionName'],
'semester' => $data['semester'],
'school_year' => $data['schoolYear'],
'scores_locked' => $data['scoresLocked'],
@@ -73,6 +73,7 @@ class QuizController extends BaseApiController
'ok' => true,
'students' => ScoreStudentResource::collection($data['students']),
'headers' => $data['headers'],
'class_section_name' => $data['classSectionName'],
'semester' => $data['semester'],
'school_year' => $data['schoolYear'],
'scores_locked' => $data['scoresLocked'],
@@ -66,6 +66,7 @@ class ScoreCommentController extends BaseApiController
'semester' => $data['semester'],
'school_year' => $data['schoolYear'],
'class_section_id' => $data['classSectionId'],
'class_section_name' => $data['classSectionName'],
'scores_locked' => $data['scoresLocked'],
]);
}
@@ -7,6 +7,8 @@ use Illuminate\Http\Resources\Json\JsonResource;
class DailyAttendanceResource extends JsonResource
{
public bool $preserveKeys = true;
public function toArray(Request $request): array
{
return [
@@ -14,6 +14,7 @@ class ScoreStudentResource extends JsonResource
'firstname' => $this->resource['firstname'] ?? null,
'lastname' => $this->resource['lastname'] ?? null,
'school_id' => $this->resource['school_id'] ?? null,
'score' => $this->resource['score'] ?? null,
'scores' => $this->resource['scores'] ?? [],
'comments' => $this->resource['comments'] ?? null,
];