fix teacher and parent routes
This commit is contained in:
Executable → Regular
Executable → Regular
@@ -12,6 +12,7 @@ use App\Http\Resources\ClassProgress\ClassProgressReportResource;
|
||||
use App\Http\Resources\ClassProgress\ClassProgressShowResource;
|
||||
use App\Models\ClassProgressAttachment;
|
||||
use App\Models\ClassProgressReport;
|
||||
use App\Models\Configuration;
|
||||
use App\Models\User;
|
||||
use App\Services\ClassProgress\ClassProgressAttachmentService;
|
||||
use App\Services\ClassProgress\ClassProgressMetaService;
|
||||
@@ -36,14 +37,43 @@ class ClassProgressController extends BaseApiController
|
||||
|
||||
public function meta(ClassProgressMetaRequest $request): JsonResponse
|
||||
{
|
||||
$classId = $request->validated('class_id');
|
||||
$sundayCount = (int) ($request->validated('sunday_count') ?? 12);
|
||||
return $this->respondSuccess(
|
||||
$this->buildMetaPayload(
|
||||
$request->validated('class_id'),
|
||||
(int) ($request->validated('sunday_count') ?? 12)
|
||||
),
|
||||
'Progress metadata loaded.'
|
||||
);
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'subject_sections' => $this->metaService->subjectSections(),
|
||||
'status_options' => $this->metaService->statusOptions(),
|
||||
'sunday_options' => $this->metaService->sundayOptionsAlignedWithCi($sundayCount),
|
||||
'curriculum' => $this->metaService->curriculumOptions($classId ? (int) $classId : null),
|
||||
/**
|
||||
* Legacy teacher portal URL for the weekly class progress submit page.
|
||||
*/
|
||||
public function legacySubmitForm(ClassProgressMetaRequest $request): JsonResponse
|
||||
{
|
||||
$auth = $this->requireAuthenticatedUser($request->user());
|
||||
if ($auth instanceof JsonResponse) {
|
||||
return $auth;
|
||||
}
|
||||
|
||||
$schoolYear = (string) (Configuration::getConfig('school_year') ?? '');
|
||||
$semester = (string) (Configuration::getConfig('semester') ?? '');
|
||||
$assignments = $this->queryService->teacherAssignments($auth, $schoolYear, $semester);
|
||||
|
||||
$requestedClassId = (int) ($request->validated('class_id') ?? 0);
|
||||
$activeClassId = $requestedClassId > 0 ? $requestedClassId : (int) ($assignments[0]['class_id'] ?? 0);
|
||||
$activeAssignment = collect($assignments)->first(
|
||||
fn (array $assignment) => (int) ($assignment['class_id'] ?? 0) === $activeClassId
|
||||
) ?? ($assignments[0] ?? []);
|
||||
$sundayCount = (int) ($request->validated('sunday_count') ?? 12);
|
||||
$payload = $this->buildMetaPayload($activeClassId > 0 ? $activeClassId : null, $sundayCount);
|
||||
$payload['classes'] = $assignments;
|
||||
$payload['defaults'] = [
|
||||
'class_id' => $activeClassId > 0 ? $activeClassId : null,
|
||||
'class_section_id' => (int) ($activeAssignment['class_section_id'] ?? 0) ?: null,
|
||||
'school_year' => $schoolYear !== '' ? $schoolYear : null,
|
||||
'semester' => $semester !== '' ? $semester : null,
|
||||
'week_start' => $this->metaService->pickDefaultWeekStart($payload['sunday_options']),
|
||||
];
|
||||
|
||||
return $this->respondSuccess($payload, 'Progress metadata loaded.');
|
||||
@@ -196,10 +226,28 @@ class ClassProgressController extends BaseApiController
|
||||
*/
|
||||
private function requireAuthenticatedUser($user): User|JsonResponse
|
||||
{
|
||||
if (!$user instanceof User) {
|
||||
$user = auth()->user();
|
||||
}
|
||||
|
||||
if (!$user instanceof User) {
|
||||
$user = $this->laravelRequest->user();
|
||||
}
|
||||
|
||||
if (!$user instanceof User) {
|
||||
return $this->respondError('Unauthorized.', Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
private function buildMetaPayload(?int $classId, int $sundayCount): array
|
||||
{
|
||||
return [
|
||||
'subject_sections' => $this->metaService->subjectSections(),
|
||||
'status_options' => $this->metaService->statusOptions(),
|
||||
'sunday_options' => $this->metaService->sundayOptionsAlignedWithCi($sundayCount),
|
||||
'curriculum' => $this->metaService->curriculumOptions($classId),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,6 +229,12 @@ class BaseApiController extends Controller
|
||||
protected function getCurrentUserId(): ?int
|
||||
{
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
if ($userId <= 0) {
|
||||
$userId = (int) (optional(auth()->user())->id ?? 0);
|
||||
}
|
||||
if ($userId <= 0) {
|
||||
$userId = (int) (optional($this->laravelRequest->user())->id ?? 0);
|
||||
}
|
||||
return $userId > 0 ? $userId : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Http\Resources\Incidents\IncidentAnalysisStudentResource;
|
||||
use App\Http\Resources\Incidents\IncidentGradeResource;
|
||||
use App\Http\Resources\Incidents\IncidentResource;
|
||||
use App\Http\Resources\Incidents\IncidentStudentOptionResource;
|
||||
use App\Models\Configuration;
|
||||
use App\Services\Incidents\CurrentIncidentService;
|
||||
use App\Services\Incidents\IncidentAnalysisService;
|
||||
use App\Services\Incidents\IncidentHistoryService;
|
||||
@@ -39,6 +40,19 @@ class IncidentController extends BaseApiController
|
||||
]);
|
||||
}
|
||||
|
||||
public function formMeta(): JsonResponse
|
||||
{
|
||||
$data = $this->currentService->listCurrent();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'incidents' => IncidentResource::collection($data['incidents']),
|
||||
'grades' => IncidentGradeResource::collection($data['grades']),
|
||||
'school_year' => Configuration::getConfig('school_year'),
|
||||
'semester' => Configuration::getConfig('semester'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function history(IncidentListRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Http\Controllers\Api\Reports;
|
||||
|
||||
use App\Http\Controllers\Api\Core\BaseApiController;
|
||||
use App\Http\Requests\Reports\ReportCards\ReportCardAcknowledgementRequest;
|
||||
use App\Http\Requests\Reports\ReportCards\ReportCardCompletenessRequest;
|
||||
use App\Http\Requests\Reports\ReportCards\ReportCardMetaRequest;
|
||||
use App\Http\Requests\Reports\ReportCards\ReportCardPdfRequest;
|
||||
@@ -14,7 +13,9 @@ use App\Http\Resources\Reports\ReportCards\ReportCardStudentMetaResource;
|
||||
use App\Models\Configuration;
|
||||
use App\Services\Reports\ReportCards\ReportCardService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ReportCardsController extends BaseApiController
|
||||
@@ -67,12 +68,28 @@ class ReportCardsController extends BaseApiController
|
||||
], $result['message'] ?? 'Success');
|
||||
}
|
||||
|
||||
public function acknowledgement(ReportCardAcknowledgementRequest $request): JsonResponse
|
||||
public function acknowledgement(Request $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$studentId = (int) ($payload['student_id'] ?? 0);
|
||||
$schoolYear = $this->resolveSchoolYear($payload['school_year'] ?? null);
|
||||
$semester = $this->resolveSemester($payload['semester'] ?? null);
|
||||
$payload = [
|
||||
'student_id' => $request->query('student_id', $request->query('studentId')),
|
||||
'school_year' => $request->query('school_year', $request->query('schoolYear')),
|
||||
'semester' => $request->query('semester'),
|
||||
];
|
||||
|
||||
$validator = Validator::make($payload, [
|
||||
'student_id' => ['required', 'integer', 'min:1'],
|
||||
'school_year' => ['nullable', 'string', 'max:20'],
|
||||
'semester' => ['nullable', 'string', 'max:20'],
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return $this->error('Validation failed.', Response::HTTP_UNPROCESSABLE_ENTITY, $validator->errors()->toArray());
|
||||
}
|
||||
|
||||
$validated = $validator->validated();
|
||||
$studentId = (int) ($validated['student_id'] ?? 0);
|
||||
$schoolYear = $this->resolveSchoolYear($validated['school_year'] ?? null);
|
||||
$semester = $this->resolveSemester($validated['semester'] ?? null);
|
||||
|
||||
$result = $this->service->acknowledgement($studentId, $schoolYear, $semester);
|
||||
|
||||
|
||||
@@ -0,0 +1,876 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Api\Core\BaseApiController;
|
||||
use App\Models\AttendanceData;
|
||||
use App\Models\AttendanceDay;
|
||||
use App\Models\AttendanceRecord;
|
||||
use App\Models\Configuration;
|
||||
use App\Models\CurrentFlag;
|
||||
use App\Models\LateSlipLog;
|
||||
use App\Models\Payment;
|
||||
use App\Models\SemesterScore;
|
||||
use App\Models\StaffAttendance;
|
||||
use App\Models\Student;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ScannerController extends BaseApiController
|
||||
{
|
||||
private const LATE_CUTOFF_CONFIG_KEY = 'attendance_late_cutoff_time';
|
||||
private const DEFAULT_LATE_CUTOFF = '10:00 AM';
|
||||
|
||||
public function process(Request $request): JsonResponse
|
||||
{
|
||||
$data = $request->json()->all();
|
||||
if (!is_array($data) || $data === []) {
|
||||
$data = $request->all();
|
||||
}
|
||||
|
||||
$badgeCode = trim((string) ($data['badgeCode'] ?? $data['BadgeCode'] ?? $data['badge_code'] ?? $data['barcode'] ?? $data['Barcode'] ?? ''));
|
||||
$stationId = trim((string) ($data['stationId'] ?? $data['StationId'] ?? $data['station_id'] ?? $data['deviceId'] ?? $data['DeviceId'] ?? ''));
|
||||
$action = strtolower(trim((string) ($data['action'] ?? 'attendance')));
|
||||
$scanStatus = strtolower(trim((string) ($data['status'] ?? 'present')));
|
||||
$scannedAt = $this->normalizeScannedAt((string) ($data['scannedAt'] ?? $data['ScannedAt'] ?? $data['scanned_at'] ?? ''));
|
||||
$operatorId = (int) (auth()->id() ?? 0);
|
||||
|
||||
if ($badgeCode === '') {
|
||||
$this->logScan($badgeCode, null, null, $action, 'rejected', $operatorId, $stationId, 'Badge code is required.', $scannedAt);
|
||||
return response()->json($this->failure('Badge code is required.'), 400);
|
||||
}
|
||||
|
||||
if (!in_array($scanStatus, ['present', 'late'], true)) {
|
||||
$scanStatus = 'present';
|
||||
}
|
||||
|
||||
$student = $this->findStudentByBadge($badgeCode);
|
||||
if ($student) {
|
||||
return $this->processStudentScan($student, $badgeCode, $stationId, $action, $scanStatus, $scannedAt, $operatorId);
|
||||
}
|
||||
|
||||
$staffUser = $this->findStaffUserByBadge($badgeCode);
|
||||
if ($staffUser) {
|
||||
return $this->processStaffScan($staffUser, $badgeCode, $stationId, $action, $scanStatus, $scannedAt, $operatorId);
|
||||
}
|
||||
|
||||
$this->logScan($badgeCode, null, null, $action, 'not_found', $operatorId, $stationId, 'Badge not found.', $scannedAt);
|
||||
return response()->json($this->failure('Badge not found.'), 404);
|
||||
}
|
||||
|
||||
private function processStudentScan(Student $student, string $badgeCode, string $stationId, string $action, string $status, string $scannedAt, int $operatorId): JsonResponse
|
||||
{
|
||||
$term = $this->currentTerm($student->toArray());
|
||||
$schoolNow = $this->schoolLocalDateTime($scannedAt);
|
||||
$date = $schoolNow->format('Y-m-d');
|
||||
$lateCutoff = $this->lateCutoffForDate($schoolNow);
|
||||
$autoLate = $this->isAfterLateCutoff($schoolNow, $lateCutoff);
|
||||
if ($autoLate) {
|
||||
$status = 'late';
|
||||
}
|
||||
|
||||
$assignment = $this->studentAssignment((int) $student->id, $term['semester'], $term['school_year']);
|
||||
if (!$assignment) {
|
||||
$this->logScan($badgeCode, 'student', (int) $student->id, $action, 'rejected', $operatorId, $stationId, 'Student has no class assignment for current term.', $scannedAt);
|
||||
return response()->json($this->failure('Student has no class assignment for current term.'), 409);
|
||||
}
|
||||
|
||||
$classSectionId = (int) $assignment['class_section_id'];
|
||||
$classId = (int) ($assignment['class_id'] ?? 0);
|
||||
$existing = $this->findAttendanceRow((int) $student->id, (string) ($student->school_id ?? ''), $date, $term['semester'], $term['school_year']);
|
||||
$oldStatus = $existing?->status;
|
||||
$now = utc_now();
|
||||
|
||||
$row = [
|
||||
'class_id' => $classId,
|
||||
'class_section_id' => $classSectionId,
|
||||
'student_id' => (int) $student->id,
|
||||
'school_id' => (string) ($student->school_id ?? ''),
|
||||
'date' => $date,
|
||||
'status' => $status,
|
||||
'reason' => null,
|
||||
'semester' => $term['semester'],
|
||||
'school_year' => $term['school_year'],
|
||||
'modified_by' => $operatorId,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
|
||||
if (Schema::hasColumn('attendance_data', 'is_reported')) {
|
||||
$row['is_reported'] = 'no';
|
||||
}
|
||||
if (Schema::hasColumn('attendance_data', 'reported')) {
|
||||
$row['reported'] = 0;
|
||||
}
|
||||
if (Schema::hasColumn('attendance_data', 'is_notified')) {
|
||||
$row['is_notified'] = 'no';
|
||||
}
|
||||
|
||||
if ($existing) {
|
||||
$existing->fill($row);
|
||||
$existing->save();
|
||||
|
||||
if (strtolower((string) $oldStatus) !== $status) {
|
||||
$this->adjustAttendanceRecord(
|
||||
$classSectionId,
|
||||
(int) $student->id,
|
||||
(string) ($student->school_id ?? ''),
|
||||
$status,
|
||||
$term['semester'],
|
||||
$term['school_year'],
|
||||
$oldStatus,
|
||||
$operatorId
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$row['created_at'] = $now;
|
||||
AttendanceData::query()->create($row);
|
||||
$this->addAttendanceRecord($classSectionId, (int) $student->id, (string) ($student->school_id ?? ''), $status, $term['semester'], $term['school_year'], $operatorId);
|
||||
}
|
||||
|
||||
$this->ensureAttendanceDay($classSectionId, $date, $term['semester'], $term['school_year']);
|
||||
$message = $status === 'late' ? 'Student late attendance recorded.' : 'Student attendance recorded.';
|
||||
$this->logScan($badgeCode, 'student', (int) $student->id, $action, 'accepted', $operatorId, $stationId, $message, $scannedAt);
|
||||
|
||||
$studentSummary = $this->studentSummary((int) $student->id, (int) ($student->parent_id ?? 0), $term['semester'], $term['school_year']);
|
||||
$person = $this->studentPayload($student->toArray(), $assignment);
|
||||
$lateSlip = null;
|
||||
$warnings = [];
|
||||
|
||||
if ($autoLate) {
|
||||
$lateSlip = $this->createLateSlipPayload(
|
||||
$student->toArray(),
|
||||
$assignment,
|
||||
$term,
|
||||
$schoolNow,
|
||||
$lateCutoff,
|
||||
$operatorId,
|
||||
$existing ? (string) ($oldStatus ?? '') : null
|
||||
);
|
||||
$warnings[] = 'Student arrived after ' . $lateCutoff->format('h:i A') . '. Late slip receipt generated.';
|
||||
}
|
||||
|
||||
return response()->json($this->successPayload([
|
||||
'personType' => 'student',
|
||||
'person' => $person,
|
||||
'Person' => $this->clientPersonPayload($person, 'student', true),
|
||||
'attendance' => [
|
||||
'status' => $status,
|
||||
'date' => $date,
|
||||
'scannedAt' => $scannedAt,
|
||||
'duplicate' => (bool) $existing,
|
||||
],
|
||||
'Attendance' => $this->clientAttendancePayload($status, $message, $scannedAt),
|
||||
'studentSummary' => $studentSummary,
|
||||
'StudentSummary' => $this->clientStudentSummaryPayload($studentSummary),
|
||||
'lateSlip' => $lateSlip,
|
||||
'LateSlip' => $lateSlip,
|
||||
'warnings' => $warnings,
|
||||
'Warnings' => $warnings,
|
||||
], $message));
|
||||
}
|
||||
|
||||
private function processStaffScan(User $user, string $badgeCode, string $stationId, string $action, string $status, string $scannedAt, int $operatorId): JsonResponse
|
||||
{
|
||||
$term = $this->currentTerm($user->toArray());
|
||||
$date = substr($scannedAt, 0, 10);
|
||||
$roleName = $this->primaryRoleName((int) $user->id);
|
||||
|
||||
StaffAttendance::upsertOne(
|
||||
(int) $user->id,
|
||||
$roleName,
|
||||
$date,
|
||||
$term['semester'],
|
||||
$term['school_year'],
|
||||
$status,
|
||||
null,
|
||||
$operatorId
|
||||
);
|
||||
|
||||
$this->logScan($badgeCode, 'staff', (int) $user->id, $action, 'accepted', $operatorId, $stationId, 'Staff attendance recorded.', $scannedAt);
|
||||
|
||||
$person = [
|
||||
'id' => (int) $user->id,
|
||||
'name' => trim((string) ($user->firstname ?? '') . ' ' . (string) ($user->lastname ?? '')),
|
||||
'email' => $user->email,
|
||||
'role' => $roleName,
|
||||
'badgeCode' => $badgeCode,
|
||||
];
|
||||
|
||||
return response()->json($this->successPayload([
|
||||
'personType' => 'staff',
|
||||
'person' => $person,
|
||||
'Person' => $this->clientPersonPayload($person, 'staff', true),
|
||||
'attendance' => [
|
||||
'status' => $status,
|
||||
'date' => $date,
|
||||
'scannedAt' => $scannedAt,
|
||||
],
|
||||
'Attendance' => $this->clientAttendancePayload($status, 'Staff attendance recorded.', $scannedAt),
|
||||
'studentSummary' => null,
|
||||
'StudentSummary' => null,
|
||||
'warnings' => [],
|
||||
'Warnings' => [],
|
||||
], 'Staff attendance recorded.'));
|
||||
}
|
||||
|
||||
private function findStudentByBadge(string $badgeCode): ?Student
|
||||
{
|
||||
return Student::query()
|
||||
->where(function ($query) use ($badgeCode) {
|
||||
$query->where('rfid_tag', $badgeCode)
|
||||
->orWhere('school_id', $badgeCode);
|
||||
})
|
||||
->first();
|
||||
}
|
||||
|
||||
private function findStaffUserByBadge(string $badgeCode): ?User
|
||||
{
|
||||
$user = User::query()
|
||||
->where(function ($query) use ($badgeCode) {
|
||||
$query->where('rfid_tag', $badgeCode);
|
||||
if (ctype_digit($badgeCode)) {
|
||||
$query->orWhere('school_id', (int) $badgeCode);
|
||||
}
|
||||
})
|
||||
->first();
|
||||
|
||||
if (!$user || !$this->isStaffUser((int) $user->id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
private function isStaffUser(int $userId): bool
|
||||
{
|
||||
$roles = DB::table('user_roles as ur')
|
||||
->selectRaw('LOWER(r.name) AS role_name')
|
||||
->leftJoin('roles as r', 'r.id', '=', 'ur.role_id')
|
||||
->where('ur.user_id', $userId)
|
||||
->get();
|
||||
|
||||
foreach ($roles as $role) {
|
||||
$name = trim((string) ($role->role_name ?? ''));
|
||||
if ($name !== '' && !in_array($name, ['parent', 'guest', 'student'], true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function primaryRoleName(int $userId): ?string
|
||||
{
|
||||
return DB::table('user_roles as ur')
|
||||
->leftJoin('roles as r', 'r.id', '=', 'ur.role_id')
|
||||
->where('ur.user_id', $userId)
|
||||
->orderBy('r.name')
|
||||
->value('r.name');
|
||||
}
|
||||
|
||||
private function studentAssignment(int $studentId, string $semester, string $schoolYear): ?array
|
||||
{
|
||||
$builder = DB::table('student_class as sc')
|
||||
->select('sc.class_section_id', 'cs.class_id', 'cs.class_section_name')
|
||||
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
|
||||
->where('sc.student_id', $studentId);
|
||||
|
||||
if (Schema::hasColumn('student_class', 'school_year')) {
|
||||
$builder->where('sc.school_year', $schoolYear);
|
||||
}
|
||||
if (Schema::hasColumn('student_class', 'semester')) {
|
||||
$builder->where('sc.semester', $semester);
|
||||
}
|
||||
|
||||
$row = $builder->orderByDesc('sc.id')->first();
|
||||
if (!$row && Schema::hasColumn('student_class', 'school_year')) {
|
||||
$row = DB::table('student_class as sc')
|
||||
->select('sc.class_section_id', 'cs.class_id', 'cs.class_section_name')
|
||||
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
|
||||
->where('sc.student_id', $studentId)
|
||||
->where('sc.school_year', $schoolYear)
|
||||
->orderByDesc('sc.id')
|
||||
->first();
|
||||
}
|
||||
|
||||
return $row ? (array) $row : null;
|
||||
}
|
||||
|
||||
private function findAttendanceRow(int $studentId, string $schoolId, string $date, string $semester, string $schoolYear): ?AttendanceData
|
||||
{
|
||||
$row = AttendanceData::query()
|
||||
->where('student_id', $studentId)
|
||||
->whereDate('date', $date)
|
||||
->where('semester', $semester)
|
||||
->where('school_year', $schoolYear)
|
||||
->first();
|
||||
|
||||
if ($row) {
|
||||
return $row;
|
||||
}
|
||||
|
||||
return AttendanceData::query()
|
||||
->where('school_id', $schoolId)
|
||||
->whereDate('date', $date)
|
||||
->where('semester', $semester)
|
||||
->where('school_year', $schoolYear)
|
||||
->first();
|
||||
}
|
||||
|
||||
private function adjustAttendanceRecord(int $classSectionId, int $studentId, string $schoolId, string $newStatus, string $semester, string $schoolYear, ?string $oldStatus, int $operatorId): void
|
||||
{
|
||||
$record = AttendanceRecord::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('semester', $semester)
|
||||
->where('school_year', $schoolYear)
|
||||
->first();
|
||||
|
||||
if (!$record) {
|
||||
$this->addAttendanceRecord($classSectionId, $studentId, $schoolId, $newStatus, $semester, $schoolYear, $operatorId);
|
||||
return;
|
||||
}
|
||||
|
||||
$presence = (int) ($record->total_presence ?? 0);
|
||||
$absence = (int) ($record->total_absence ?? 0);
|
||||
$late = (int) ($record->total_late ?? 0);
|
||||
|
||||
if ($oldStatus === 'present') {
|
||||
$presence--;
|
||||
} elseif ($oldStatus === 'absent') {
|
||||
$absence--;
|
||||
} elseif ($oldStatus === 'late') {
|
||||
$late--;
|
||||
}
|
||||
|
||||
if ($newStatus === 'present') {
|
||||
$presence++;
|
||||
} elseif ($newStatus === 'absent') {
|
||||
$absence++;
|
||||
} elseif ($newStatus === 'late') {
|
||||
$late++;
|
||||
}
|
||||
|
||||
$record->fill([
|
||||
'class_section_id' => $classSectionId,
|
||||
'total_presence' => max(0, $presence),
|
||||
'total_absence' => max(0, $absence),
|
||||
'total_late' => max(0, $late),
|
||||
'modified_by' => $operatorId,
|
||||
'updated_at' => utc_now(),
|
||||
]);
|
||||
$record->save();
|
||||
}
|
||||
|
||||
private function addAttendanceRecord(int $classSectionId, int $studentId, string $schoolId, string $status, string $semester, string $schoolYear, int $operatorId): void
|
||||
{
|
||||
$record = AttendanceRecord::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('semester', $semester)
|
||||
->where('school_year', $schoolYear)
|
||||
->first();
|
||||
|
||||
$field = $status === 'late' ? 'total_late' : ($status === 'absent' ? 'total_absence' : 'total_presence');
|
||||
if ($record) {
|
||||
$record->fill([
|
||||
'class_section_id' => $classSectionId,
|
||||
$field => (int) ($record->{$field} ?? 0) + 1,
|
||||
'total_attendance' => (int) ($record->total_attendance ?? 0) + 1,
|
||||
'modified_by' => $operatorId,
|
||||
'updated_at' => utc_now(),
|
||||
]);
|
||||
$record->save();
|
||||
return;
|
||||
}
|
||||
|
||||
AttendanceRecord::query()->create([
|
||||
'class_section_id' => $classSectionId,
|
||||
'student_id' => $studentId,
|
||||
'school_id' => $schoolId,
|
||||
'total_presence' => $status === 'present' ? 1 : 0,
|
||||
'total_absence' => $status === 'absent' ? 1 : 0,
|
||||
'total_late' => $status === 'late' ? 1 : 0,
|
||||
'total_attendance' => 1,
|
||||
'semester' => $semester,
|
||||
'school_year' => $schoolYear,
|
||||
'modified_by' => $operatorId,
|
||||
'created_at' => utc_now(),
|
||||
'updated_at' => utc_now(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function ensureAttendanceDay(int $classSectionId, string $date, string $semester, string $schoolYear): void
|
||||
{
|
||||
if (AttendanceDay::findBySectionDate($classSectionId, $date, $semester, $schoolYear)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AttendanceDay::query()->create([
|
||||
'class_section_id' => $classSectionId,
|
||||
'date' => $date,
|
||||
'status' => 'draft',
|
||||
'semester' => $semester,
|
||||
'school_year' => $schoolYear,
|
||||
'created_at' => utc_now(),
|
||||
'updated_at' => utc_now(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function studentPayload(array $student, array $assignment): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) ($student['id'] ?? 0),
|
||||
'schoolId' => $student['school_id'] ?? null,
|
||||
'name' => trim((string) ($student['firstname'] ?? '') . ' ' . (string) ($student['lastname'] ?? '')),
|
||||
'firstName' => $student['firstname'] ?? null,
|
||||
'lastName' => $student['lastname'] ?? null,
|
||||
'grade' => $student['registration_grade'] ?? null,
|
||||
'classSectionId' => isset($assignment['class_section_id']) ? (int) $assignment['class_section_id'] : null,
|
||||
'classSectionName' => $assignment['class_section_name'] ?? null,
|
||||
'badgeCode' => !empty($student['rfid_tag']) ? $student['rfid_tag'] : ($student['school_id'] ?? null),
|
||||
];
|
||||
}
|
||||
|
||||
private function studentSummary(int $studentId, int $parentId, string $semester, string $schoolYear): array
|
||||
{
|
||||
$attendance = AttendanceRecord::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('semester', $semester)
|
||||
->where('school_year', $schoolYear)
|
||||
->first();
|
||||
|
||||
$score = SemesterScore::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('semester', $semester)
|
||||
->where('school_year', $schoolYear)
|
||||
->orderByDesc('id')
|
||||
->first();
|
||||
|
||||
$flags = CurrentFlag::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('school_year', $schoolYear)
|
||||
->whereIn('flag_state', ['open', 'Opened', 'Open'])
|
||||
->orderByDesc('flag_datetime')
|
||||
->limit(10)
|
||||
->get();
|
||||
|
||||
$payments = Payment::query()
|
||||
->where('parent_id', $parentId)
|
||||
->where('school_year', $schoolYear)
|
||||
->orderByDesc('payment_date')
|
||||
->limit(5)
|
||||
->get();
|
||||
|
||||
return [
|
||||
'attendance' => [
|
||||
'present' => (int) ($attendance->total_presence ?? 0),
|
||||
'late' => (int) ($attendance->total_late ?? 0),
|
||||
'absent' => (int) ($attendance->total_absence ?? 0),
|
||||
'total' => (int) ($attendance->total_attendance ?? 0),
|
||||
'score' => isset($score?->attendance_score) ? (float) $score->attendance_score : null,
|
||||
],
|
||||
'grading' => [
|
||||
'homeworkAvg' => isset($score?->homework_avg) ? (float) $score->homework_avg : null,
|
||||
'quizAvg' => isset($score?->quiz_avg) ? (float) $score->quiz_avg : null,
|
||||
'projectAvg' => isset($score?->project_avg) ? (float) $score->project_avg : null,
|
||||
'midtermExamScore' => isset($score?->midterm_exam_score) ? (float) $score->midterm_exam_score : null,
|
||||
'finalExamScore' => isset($score?->final_exam_score) ? (float) $score->final_exam_score : null,
|
||||
'semesterScore' => isset($score?->semester_score) ? (float) $score->semester_score : null,
|
||||
],
|
||||
'discipline' => $flags->map(static fn (CurrentFlag $flag): array => [
|
||||
'id' => (int) $flag->id,
|
||||
'flag' => $flag->flag,
|
||||
'state' => $flag->flag_state,
|
||||
'description' => $flag->open_description,
|
||||
'date' => $flag->flag_datetime?->toDateTimeString(),
|
||||
])->all(),
|
||||
'payments' => $payments->map(static fn (Payment $payment): array => [
|
||||
'id' => (int) $payment->id,
|
||||
'invoiceId' => isset($payment->invoice_id) ? (int) $payment->invoice_id : null,
|
||||
'totalAmount' => isset($payment->total_amount) ? (float) $payment->total_amount : null,
|
||||
'paidAmount' => isset($payment->paid_amount) ? (float) $payment->paid_amount : null,
|
||||
'balance' => isset($payment->balance) ? (float) $payment->balance : null,
|
||||
'status' => $payment->status,
|
||||
'date' => $payment->payment_date?->toDateTimeString(),
|
||||
])->all(),
|
||||
];
|
||||
}
|
||||
|
||||
private function clientPersonPayload(array $person, string $type, bool $active): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) ($person['id'] ?? 0),
|
||||
'name' => (string) ($person['name'] ?? ''),
|
||||
'type' => $type,
|
||||
'barcode' => (string) ($person['badgeCode'] ?? $person['schoolId'] ?? ''),
|
||||
'active' => $active,
|
||||
];
|
||||
}
|
||||
|
||||
private function clientAttendancePayload(string $status, string $message, string $scannedAt): array
|
||||
{
|
||||
return [
|
||||
'status' => $status,
|
||||
'message' => $message,
|
||||
'time' => gmdate('c', strtotime($scannedAt) ?: time()),
|
||||
];
|
||||
}
|
||||
|
||||
private function clientStudentSummaryPayload(array $summary): array
|
||||
{
|
||||
$attendance = $summary['attendance'] ?? [];
|
||||
$payments = $summary['payments'] ?? [];
|
||||
$lastAttendance = $attendance['lastAttendance'] ?? null;
|
||||
$outstandingBalance = 0.0;
|
||||
$paymentStatus = 'Unknown';
|
||||
|
||||
if (!empty($payments)) {
|
||||
$latest = $payments[0];
|
||||
$paymentStatus = (string) ($latest['status'] ?? 'Unknown');
|
||||
$outstandingBalance = (float) ($latest['balance'] ?? 0);
|
||||
}
|
||||
|
||||
$issues = $this->scanIssues($summary);
|
||||
|
||||
return [
|
||||
'lastAttendance' => $lastAttendance,
|
||||
'attendanceCount' => (int) ($attendance['total'] ?? 0),
|
||||
'paymentStatus' => $paymentStatus,
|
||||
'outstandingBalance' => $outstandingBalance,
|
||||
'packageStatus' => $outstandingBalance > 0 ? 'Balance due' : 'Clear',
|
||||
'issueLevel' => $this->highestIssueLevel($issues),
|
||||
'issues' => $issues,
|
||||
];
|
||||
}
|
||||
|
||||
private function scanIssues(array $summary): array
|
||||
{
|
||||
$issues = [];
|
||||
$payments = $summary['payments'] ?? [];
|
||||
if (!empty($payments)) {
|
||||
$latest = $payments[0];
|
||||
$balance = (float) ($latest['balance'] ?? 0);
|
||||
$status = strtolower(trim((string) ($latest['status'] ?? '')));
|
||||
if ($balance > 0) {
|
||||
$issues[] = [
|
||||
'category' => 'payment',
|
||||
'severity' => 'critical',
|
||||
'title' => 'Payment balance due',
|
||||
'detail' => '$' . number_format($balance, 2) . ' outstanding',
|
||||
];
|
||||
} elseif ($status !== '' && !in_array($status, ['paid', 'full', 'full payment', 'complete', 'completed'], true)) {
|
||||
$issues[] = [
|
||||
'category' => 'payment',
|
||||
'severity' => 'warning',
|
||||
'title' => 'Payment status needs review',
|
||||
'detail' => (string) ($latest['status'] ?? ''),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$grading = $summary['grading'] ?? [];
|
||||
$semesterScore = $grading['semesterScore'] ?? null;
|
||||
if ($semesterScore !== null && (float) $semesterScore < 70) {
|
||||
$issues[] = [
|
||||
'category' => 'grades',
|
||||
'severity' => (float) $semesterScore < 60 ? 'critical' : 'warning',
|
||||
'title' => 'Grade concern',
|
||||
'detail' => 'Semester score ' . number_format((float) $semesterScore, 1),
|
||||
];
|
||||
}
|
||||
|
||||
foreach (['homeworkAvg' => 'Homework', 'quizAvg' => 'Quiz', 'projectAvg' => 'Project'] as $key => $label) {
|
||||
if (isset($grading[$key]) && $grading[$key] !== null && (float) $grading[$key] < 70) {
|
||||
$issues[] = [
|
||||
'category' => 'grades',
|
||||
'severity' => 'warning',
|
||||
'title' => $label . ' average below target',
|
||||
'detail' => number_format((float) $grading[$key], 1),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$attendance = $summary['attendance'] ?? [];
|
||||
if ((int) ($attendance['late'] ?? 0) >= 3) {
|
||||
$issues[] = [
|
||||
'category' => 'attendance',
|
||||
'severity' => 'warning',
|
||||
'title' => 'Repeated lateness',
|
||||
'detail' => (int) $attendance['late'] . ' late attendance records',
|
||||
];
|
||||
}
|
||||
|
||||
foreach (($summary['discipline'] ?? []) as $flag) {
|
||||
$flagType = strtolower(trim((string) ($flag['flag'] ?? '')));
|
||||
$category = $this->scanIssueCategory($flagType);
|
||||
$issues[] = [
|
||||
'category' => $category,
|
||||
'severity' => in_array($category, ['behavior', 'discipline'], true) ? 'critical' : 'warning',
|
||||
'title' => $this->scanIssueTitle($flagType, $category),
|
||||
'detail' => trim((string) ($flag['description'] ?? '')) ?: null,
|
||||
];
|
||||
}
|
||||
|
||||
return $issues;
|
||||
}
|
||||
|
||||
private function scanIssueCategory(string $flagType): string
|
||||
{
|
||||
if (str_contains($flagType, 'payment')) {
|
||||
return 'payment';
|
||||
}
|
||||
if (str_contains($flagType, 'grade')) {
|
||||
return 'grades';
|
||||
}
|
||||
if (str_contains($flagType, 'attendance') || str_contains($flagType, 'tard')) {
|
||||
return 'attendance';
|
||||
}
|
||||
if (
|
||||
str_contains($flagType, 'behavior')
|
||||
|| str_contains($flagType, 'defiance')
|
||||
|| str_contains($flagType, 'disrespect')
|
||||
|| str_contains($flagType, 'bullying')
|
||||
|| str_contains($flagType, 'fighting')
|
||||
|| str_contains($flagType, 'threat')
|
||||
|| str_contains($flagType, 'harassment')
|
||||
) {
|
||||
return 'behavior';
|
||||
}
|
||||
|
||||
return 'discipline';
|
||||
}
|
||||
|
||||
private function scanIssueTitle(string $flagType, string $category): string
|
||||
{
|
||||
$label = trim(str_replace('_', ' ', $flagType));
|
||||
if ($label !== '') {
|
||||
return ucwords($label) . ' flag';
|
||||
}
|
||||
|
||||
return match ($category) {
|
||||
'payment' => 'Payment flag',
|
||||
'grades' => 'Grade flag',
|
||||
'attendance' => 'Attendance flag',
|
||||
'behavior' => 'Behavior flag',
|
||||
default => 'Discipline flag',
|
||||
};
|
||||
}
|
||||
|
||||
private function highestIssueLevel(array $issues): string
|
||||
{
|
||||
$level = 'clear';
|
||||
foreach ($issues as $issue) {
|
||||
$severity = strtolower((string) ($issue['severity'] ?? ''));
|
||||
if ($severity === 'critical') {
|
||||
return 'critical';
|
||||
}
|
||||
if ($severity === 'warning') {
|
||||
$level = 'warning';
|
||||
} elseif ($severity === 'info' && $level === 'clear') {
|
||||
$level = 'info';
|
||||
}
|
||||
}
|
||||
|
||||
return $level;
|
||||
}
|
||||
|
||||
private function createLateSlipPayload(array $student, array $assignment, array $term, \DateTimeImmutable $schoolNow, \DateTimeImmutable $lateCutoff, int $operatorId, ?string $previousStatus): array
|
||||
{
|
||||
$studentName = trim((string) ($student['firstname'] ?? '') . ' ' . (string) ($student['lastname'] ?? ''));
|
||||
$grade = (string) ($student['registration_grade'] ?? $assignment['class_section_name'] ?? '');
|
||||
$cutoffLabel = $lateCutoff->format('h:i A');
|
||||
$reason = 'Arrived after ' . $cutoffLabel;
|
||||
$adminName = $this->operatorName($operatorId);
|
||||
$slip = [
|
||||
'school_year' => $term['school_year'],
|
||||
'semester' => $term['semester'],
|
||||
'student_name' => $studentName,
|
||||
'slip_date' => $schoolNow->format('Y-m-d'),
|
||||
'time_in' => $schoolNow->format('H:i:s'),
|
||||
'grade' => $grade,
|
||||
'reason' => $reason,
|
||||
'admin_name' => $adminName,
|
||||
];
|
||||
|
||||
$logId = null;
|
||||
$logged = false;
|
||||
if ($previousStatus !== 'late') {
|
||||
try {
|
||||
$created = LateSlipLog::query()->create([
|
||||
'school_year' => $slip['school_year'],
|
||||
'semester' => $slip['semester'],
|
||||
'student_name' => $slip['student_name'],
|
||||
'slip_date' => $slip['slip_date'],
|
||||
'time_in' => $slip['time_in'],
|
||||
'grade' => $slip['grade'],
|
||||
'reason' => $slip['reason'],
|
||||
'admin_name' => $slip['admin_name'],
|
||||
'printed_by' => $operatorId ?: null,
|
||||
'printed_at' => utc_now(),
|
||||
]);
|
||||
$logId = (int) ($created->id ?? 0);
|
||||
$logged = $logId > 0;
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Badge scan late slip log failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$displayDate = $schoolNow->format('m/d/Y');
|
||||
$displayTime = $schoolNow->format('h:i A');
|
||||
|
||||
return [
|
||||
'required' => true,
|
||||
'logged' => $logged,
|
||||
'logId' => $logId ?: null,
|
||||
'logUrl' => url('/administrator/late_slips'),
|
||||
'schoolYear' => $slip['school_year'],
|
||||
'semester' => $slip['semester'],
|
||||
'studentName' => $studentName,
|
||||
'date' => $displayDate,
|
||||
'timeIn' => $displayTime,
|
||||
'grade' => $grade,
|
||||
'reason' => $reason,
|
||||
'adminName' => $adminName,
|
||||
'cutoffTime' => $cutoffLabel,
|
||||
'message' => 'Late slip receipt generated for arrival after ' . $cutoffLabel . '.',
|
||||
'text' => implode("\n", [
|
||||
'Al Rahma School at ISGL',
|
||||
'School Year: ' . $slip['school_year'],
|
||||
'Student Name: ' . $studentName,
|
||||
'Date: ' . $displayDate . ' Time In: ' . $displayTime,
|
||||
'Grade: ' . $grade,
|
||||
'Reason: ' . $reason,
|
||||
'Admin: ' . $adminName,
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
private function isAfterLateCutoff(\DateTimeImmutable $schoolNow, \DateTimeImmutable $cutoff): bool
|
||||
{
|
||||
return $schoolNow >= $cutoff;
|
||||
}
|
||||
|
||||
private function lateCutoffForDate(\DateTimeImmutable $schoolNow): \DateTimeImmutable
|
||||
{
|
||||
$configured = trim((string) (Configuration::getConfig(self::LATE_CUTOFF_CONFIG_KEY) ?: self::DEFAULT_LATE_CUTOFF));
|
||||
$timezone = $schoolNow->getTimezone();
|
||||
$date = $schoolNow->format('Y-m-d');
|
||||
|
||||
foreach (['H:i:s', 'H:i', 'g:i A', 'h:i A', 'g:i a', 'h:i a'] as $format) {
|
||||
$parsed = \DateTimeImmutable::createFromFormat('!' . $format, $configured, $timezone);
|
||||
if ($parsed instanceof \DateTimeImmutable) {
|
||||
return $schoolNow->setTime((int) $parsed->format('H'), (int) $parsed->format('i'), (int) $parsed->format('s'));
|
||||
}
|
||||
}
|
||||
|
||||
$timestamp = strtotime($date . ' ' . $configured);
|
||||
if ($timestamp !== false) {
|
||||
return (new \DateTimeImmutable('@' . $timestamp))->setTimezone($timezone);
|
||||
}
|
||||
|
||||
Log::warning(self::LATE_CUTOFF_CONFIG_KEY . ' has invalid value "' . $configured . '"; using ' . self::DEFAULT_LATE_CUTOFF);
|
||||
return $schoolNow->setTime(10, 0, 0);
|
||||
}
|
||||
|
||||
private function schoolLocalDateTime(string $scannedAt): \DateTimeImmutable
|
||||
{
|
||||
$timezone = new \DateTimeZone('America/New_York');
|
||||
try {
|
||||
$utc = new \DateTimeZone('UTC');
|
||||
return (new \DateTimeImmutable($scannedAt, $utc))->setTimezone($timezone);
|
||||
} catch (\Throwable) {
|
||||
return new \DateTimeImmutable('now', $timezone);
|
||||
}
|
||||
}
|
||||
|
||||
private function operatorName(int $operatorId): string
|
||||
{
|
||||
if ($operatorId > 0) {
|
||||
try {
|
||||
$user = User::query()->select('firstname', 'lastname', 'email')->find($operatorId);
|
||||
if ($user) {
|
||||
$name = trim((string) ($user->firstname ?? '') . ' ' . (string) ($user->lastname ?? ''));
|
||||
if ($name !== '') {
|
||||
return $name;
|
||||
}
|
||||
|
||||
return (string) ($user->email ?? '');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Badge scan operator lookup failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function currentTerm(array $row): array
|
||||
{
|
||||
$semester = (string) (Configuration::getConfig('semester') ?: ($row['semester'] ?? ''));
|
||||
$schoolYear = (string) (Configuration::getConfig('school_year') ?: ($row['school_year'] ?? ''));
|
||||
|
||||
return [
|
||||
'semester' => $semester,
|
||||
'school_year' => $schoolYear,
|
||||
];
|
||||
}
|
||||
|
||||
private function normalizeScannedAt(string $value): string
|
||||
{
|
||||
if ($value !== '') {
|
||||
$timestamp = strtotime($value);
|
||||
if ($timestamp !== false) {
|
||||
return date('Y-m-d H:i:s', $timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
return utc_now();
|
||||
}
|
||||
|
||||
private function logScan(string $badgeCode, ?string $personType, ?int $personId, string $action, string $status, int $operatorId, string $stationId, string $message, string $scannedAt): void
|
||||
{
|
||||
try {
|
||||
if (!Schema::hasTable('badge_scan_logs')) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::table('badge_scan_logs')->insert([
|
||||
'badge_code' => $badgeCode,
|
||||
'person_type' => $personType,
|
||||
'person_id' => $personId,
|
||||
'scan_action' => $action,
|
||||
'scan_status' => $status,
|
||||
'operator_user_id' => $operatorId ?: null,
|
||||
'station_id' => $stationId ?: null,
|
||||
'message' => $message,
|
||||
'scanned_at' => $scannedAt,
|
||||
'created_at' => utc_now(),
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Badge scan log failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function successPayload(array $payload, string $message): array
|
||||
{
|
||||
return [
|
||||
'ok' => true,
|
||||
'status' => true,
|
||||
'success' => true,
|
||||
'message' => $message,
|
||||
] + $payload;
|
||||
}
|
||||
|
||||
private function failure(string $message): array
|
||||
{
|
||||
return [
|
||||
'ok' => false,
|
||||
'status' => false,
|
||||
'success' => false,
|
||||
'message' => $message,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ class FinalController extends BaseApiController
|
||||
{
|
||||
$data = array_merge($request->query->all(), $request->all(), $request->json()->all());
|
||||
$validator = Validator::make($data, [
|
||||
'class_section_id' => ['required', 'integer', 'min:1'],
|
||||
'class_section_id' => ['nullable', 'integer', 'min:1'],
|
||||
'semester' => ['nullable', 'string', 'max:50'],
|
||||
'school_year' => ['nullable', 'string', 'max:50'],
|
||||
]);
|
||||
@@ -38,7 +38,31 @@ class FinalController extends BaseApiController
|
||||
}
|
||||
|
||||
$payload = $validator->validated();
|
||||
$data = $this->service->list('final_exam', (int) $payload['class_section_id'], $payload['semester'] ?? null, $payload['school_year'] ?? null);
|
||||
$classSectionId = isset($payload['class_section_id']) ? (int) $payload['class_section_id'] : null;
|
||||
|
||||
if ($classSectionId === null || $classSectionId <= 0) {
|
||||
$userId = $this->authenticatedUserIdOrUnauthorized();
|
||||
if ($userId instanceof JsonResponse) {
|
||||
return $userId;
|
||||
}
|
||||
|
||||
$classSectionId = $this->service->resolveClassSectionIdForTeacher(
|
||||
$userId,
|
||||
null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
if ($classSectionId === null) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
'class_section_id' => ['Provide class_section_id or assign this account to a class for the selected school year.'],
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
$data = $this->service->list('final_exam', $classSectionId, $payload['semester'] ?? null, $payload['school_year'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
|
||||
@@ -26,7 +26,7 @@ class HomeworkController extends BaseApiController
|
||||
{
|
||||
$data = array_merge($request->query->all(), $request->all(), $request->json()->all());
|
||||
$validator = Validator::make($data, [
|
||||
'class_section_id' => ['required', 'integer', 'min:1'],
|
||||
'class_section_id' => ['nullable', 'integer', 'min:1'],
|
||||
'semester' => ['nullable', 'string', 'max:50'],
|
||||
'school_year' => ['nullable', 'string', 'max:50'],
|
||||
]);
|
||||
@@ -39,8 +39,32 @@ class HomeworkController extends BaseApiController
|
||||
}
|
||||
|
||||
$payload = $validator->validated();
|
||||
$classSectionId = isset($payload['class_section_id']) ? (int) $payload['class_section_id'] : null;
|
||||
|
||||
if ($classSectionId === null || $classSectionId <= 0) {
|
||||
$userId = $this->authenticatedUserIdOrUnauthorized();
|
||||
if ($userId instanceof JsonResponse) {
|
||||
return $userId;
|
||||
}
|
||||
|
||||
$classSectionId = $this->service->resolveClassSectionIdForTeacher(
|
||||
$userId,
|
||||
null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
if ($classSectionId === null) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
'class_section_id' => ['Provide class_section_id or assign this account to a class for the selected school year.'],
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
$data = $this->service->list(
|
||||
(int) $payload['class_section_id'],
|
||||
$classSectionId,
|
||||
$payload['semester'] ?? null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
@@ -25,7 +25,7 @@ class MidtermController extends BaseApiController
|
||||
{
|
||||
$data = array_merge($request->query->all(), $request->all(), $request->json()->all());
|
||||
$validator = Validator::make($data, [
|
||||
'class_section_id' => ['required', 'integer', 'min:1'],
|
||||
'class_section_id' => ['nullable', 'integer', 'min:1'],
|
||||
'semester' => ['nullable', 'string', 'max:50'],
|
||||
'school_year' => ['nullable', 'string', 'max:50'],
|
||||
]);
|
||||
@@ -38,7 +38,31 @@ class MidtermController extends BaseApiController
|
||||
}
|
||||
|
||||
$payload = $validator->validated();
|
||||
$data = $this->service->list('midterm_exam', (int) $payload['class_section_id'], $payload['semester'] ?? null, $payload['school_year'] ?? null);
|
||||
$classSectionId = isset($payload['class_section_id']) ? (int) $payload['class_section_id'] : null;
|
||||
|
||||
if ($classSectionId === null || $classSectionId <= 0) {
|
||||
$userId = $this->authenticatedUserIdOrUnauthorized();
|
||||
if ($userId instanceof JsonResponse) {
|
||||
return $userId;
|
||||
}
|
||||
|
||||
$classSectionId = $this->service->resolveClassSectionIdForTeacher(
|
||||
$userId,
|
||||
null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
if ($classSectionId === null) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
'class_section_id' => ['Provide class_section_id or assign this account to a class for the selected school year.'],
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
$data = $this->service->list('midterm_exam', $classSectionId, $payload['semester'] ?? null, $payload['school_year'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
|
||||
@@ -25,7 +25,7 @@ class ParticipationController extends BaseApiController
|
||||
{
|
||||
$data = array_merge($request->query->all(), $request->all(), $request->json()->all());
|
||||
$validator = Validator::make($data, [
|
||||
'class_section_id' => ['required', 'integer', 'min:1'],
|
||||
'class_section_id' => ['nullable', 'integer', 'min:1'],
|
||||
'semester' => ['nullable', 'string', 'max:50'],
|
||||
'school_year' => ['nullable', 'string', 'max:50'],
|
||||
]);
|
||||
@@ -38,8 +38,32 @@ class ParticipationController extends BaseApiController
|
||||
}
|
||||
|
||||
$payload = $validator->validated();
|
||||
$classSectionId = isset($payload['class_section_id']) ? (int) $payload['class_section_id'] : null;
|
||||
|
||||
if ($classSectionId === null || $classSectionId <= 0) {
|
||||
$userId = $this->authenticatedUserIdOrUnauthorized();
|
||||
if ($userId instanceof JsonResponse) {
|
||||
return $userId;
|
||||
}
|
||||
|
||||
$classSectionId = $this->service->resolveClassSectionIdForTeacher(
|
||||
$userId,
|
||||
null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
if ($classSectionId === null) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
'class_section_id' => ['Provide class_section_id or assign this account to a class for the selected school year.'],
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
$data = $this->service->list(
|
||||
(int) $payload['class_section_id'],
|
||||
$classSectionId,
|
||||
$payload['semester'] ?? null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
@@ -26,7 +26,7 @@ class ProjectController extends BaseApiController
|
||||
{
|
||||
$data = array_merge($request->query->all(), $request->all(), $request->json()->all());
|
||||
$validator = Validator::make($data, [
|
||||
'class_section_id' => ['required', 'integer', 'min:1'],
|
||||
'class_section_id' => ['nullable', 'integer', 'min:1'],
|
||||
'semester' => ['nullable', 'string', 'max:50'],
|
||||
'school_year' => ['nullable', 'string', 'max:50'],
|
||||
]);
|
||||
@@ -39,8 +39,32 @@ class ProjectController extends BaseApiController
|
||||
}
|
||||
|
||||
$payload = $validator->validated();
|
||||
$classSectionId = isset($payload['class_section_id']) ? (int) $payload['class_section_id'] : null;
|
||||
|
||||
if ($classSectionId === null || $classSectionId <= 0) {
|
||||
$userId = $this->authenticatedUserIdOrUnauthorized();
|
||||
if ($userId instanceof JsonResponse) {
|
||||
return $userId;
|
||||
}
|
||||
|
||||
$classSectionId = $this->service->resolveClassSectionIdForTeacher(
|
||||
$userId,
|
||||
null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
if ($classSectionId === null) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
'class_section_id' => ['Provide class_section_id or assign this account to a class for the selected school year.'],
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
$data = $this->service->list(
|
||||
(int) $payload['class_section_id'],
|
||||
$classSectionId,
|
||||
$payload['semester'] ?? null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
@@ -26,7 +26,7 @@ class QuizController extends BaseApiController
|
||||
{
|
||||
$data = array_merge($request->query->all(), $request->all(), $request->json()->all());
|
||||
$validator = Validator::make($data, [
|
||||
'class_section_id' => ['required', 'integer', 'min:1'],
|
||||
'class_section_id' => ['nullable', 'integer', 'min:1'],
|
||||
'semester' => ['nullable', 'string', 'max:50'],
|
||||
'school_year' => ['nullable', 'string', 'max:50'],
|
||||
]);
|
||||
@@ -39,8 +39,32 @@ class QuizController extends BaseApiController
|
||||
}
|
||||
|
||||
$payload = $validator->validated();
|
||||
$classSectionId = isset($payload['class_section_id']) ? (int) $payload['class_section_id'] : null;
|
||||
|
||||
if ($classSectionId === null || $classSectionId <= 0) {
|
||||
$userId = $this->authenticatedUserIdOrUnauthorized();
|
||||
if ($userId instanceof JsonResponse) {
|
||||
return $userId;
|
||||
}
|
||||
|
||||
$classSectionId = $this->service->resolveClassSectionIdForTeacher(
|
||||
$userId,
|
||||
null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
if ($classSectionId === null) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
'class_section_id' => ['Provide class_section_id or assign this account to a class for the selected school year.'],
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
$data = $this->service->list(
|
||||
(int) $payload['class_section_id'],
|
||||
$classSectionId,
|
||||
$payload['semester'] ?? null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
@@ -43,7 +43,23 @@ class SchoolCalendarController extends BaseApiController
|
||||
|
||||
public function index(SchoolCalendarIndexRequest $request): JsonResponse
|
||||
{
|
||||
$filters = $request->validated();
|
||||
return $this->respondWithEvents($request->validated());
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy teacher portal URL: same response as
|
||||
* GET /api/v1/settings/school-calendar/events?audience=teacher
|
||||
*/
|
||||
public function teacherCalendarLegacy(SchoolCalendarIndexRequest $request): JsonResponse
|
||||
{
|
||||
return $this->respondWithEvents([
|
||||
...$request->validated(),
|
||||
'audience' => 'teacher',
|
||||
]);
|
||||
}
|
||||
|
||||
private function respondWithEvents(array $filters): JsonResponse
|
||||
{
|
||||
$audience = $filters['audience'] ?? null;
|
||||
$includeMeetings = array_key_exists('include_meetings', $filters)
|
||||
? (bool) $filters['include_meetings']
|
||||
|
||||
Executable → Regular
@@ -100,6 +100,61 @@ class StudentController extends BaseApiController
|
||||
]);
|
||||
}
|
||||
|
||||
public function scoreCardSelectable(Request $request): JsonResponse
|
||||
{
|
||||
$data = array_merge($request->query->all(), $request->all(), $request->json()->all());
|
||||
$validator = Validator::make($data, [
|
||||
'q' => ['nullable', 'string', 'max:255'],
|
||||
'limit' => ['nullable', 'integer', 'min:1', 'max:1000'],
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => $validator->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$payload = $validator->validated();
|
||||
$query = trim((string) ($payload['q'] ?? ''));
|
||||
$limit = (int) ($payload['limit'] ?? ($query !== '' ? 50 : 1000));
|
||||
|
||||
$studentsQuery = Student::query()
|
||||
->select('id', 'school_id', 'firstname', 'lastname', 'is_active')
|
||||
->where('is_active', 1);
|
||||
|
||||
if ($query !== '') {
|
||||
$studentsQuery->where(function ($builder) use ($query) {
|
||||
$builder->where('firstname', 'like', '%' . $query . '%')
|
||||
->orWhere('lastname', 'like', '%' . $query . '%')
|
||||
->orWhere('school_id', 'like', '%' . $query . '%');
|
||||
});
|
||||
}
|
||||
|
||||
$students = $studentsQuery
|
||||
->orderBy('lastname')
|
||||
->orderBy('firstname')
|
||||
->limit($limit)
|
||||
->get()
|
||||
->map(fn (Student $student) => [
|
||||
'id' => (int) $student->id,
|
||||
'student_id' => (int) $student->id,
|
||||
'school_id' => (string) ($student->school_id ?? ''),
|
||||
'firstname' => (string) ($student->firstname ?? ''),
|
||||
'lastname' => (string) ($student->lastname ?? ''),
|
||||
'name' => trim(((string) ($student->firstname ?? '')) . ' ' . ((string) ($student->lastname ?? ''))),
|
||||
'is_active' => (int) ($student->is_active ?? 0),
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'students' => $students,
|
||||
'selectable' => $students,
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(int $studentId): JsonResponse
|
||||
{
|
||||
$student = Student::query()->find($studentId);
|
||||
|
||||
@@ -8,7 +8,7 @@ class ClassPreparationAdjustmentRequest extends ApiFormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
return $this->user() !== null || auth()->user() !== null;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
|
||||
@@ -8,7 +8,7 @@ class ClassPreparationIndexRequest extends ApiFormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
return $this->user() !== null || auth()->user() !== null;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
|
||||
@@ -8,7 +8,7 @@ class ClassPreparationMarkPrintedRequest extends ApiFormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
return $this->user() !== null || auth()->user() !== null;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
|
||||
@@ -8,7 +8,7 @@ class ClassPreparationPrintRequest extends ApiFormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
return $this->user() !== null || auth()->user() !== null;
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
|
||||
@@ -8,7 +8,7 @@ class ClassSectionIndexRequest extends ApiFormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
return $this->user() !== null || auth()->user() !== null;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
|
||||
@@ -9,7 +9,7 @@ class ClassSectionStoreRequest extends ApiFormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
return $this->user() !== null || auth()->user() !== null;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
|
||||
@@ -9,7 +9,7 @@ class ClassSectionUpdateRequest extends ApiFormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
return $this->user() !== null || auth()->user() !== null;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
|
||||
@@ -8,7 +8,7 @@ class IpBanIndexRequest extends ApiFormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
return $this->user() !== null || auth()->user() !== null;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
|
||||
@@ -9,7 +9,7 @@ class IpBanRequest extends ApiFormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
return $this->user() !== null || auth()->user() !== null;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
|
||||
@@ -9,7 +9,7 @@ class IpUnbanRequest extends ApiFormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
return $this->user() !== null || auth()->user() !== null;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
|
||||
@@ -8,17 +8,18 @@ class UserWithRolesResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
$roles = $this['roles'] ?? [];
|
||||
$row = is_array($this->resource) ? $this->resource : (array) $this->resource;
|
||||
$roles = $row['roles'] ?? [];
|
||||
if (is_string($roles)) {
|
||||
$roles = array_filter(array_map('trim', explode(',', $roles)));
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => (int) ($this['id'] ?? 0),
|
||||
'firstname' => (string) ($this['firstname'] ?? ''),
|
||||
'lastname' => (string) ($this['lastname'] ?? ''),
|
||||
'email' => (string) ($this['email'] ?? ''),
|
||||
'account_id' => (string) ($this['account_id'] ?? ''),
|
||||
'id' => (int) ($row['id'] ?? 0),
|
||||
'firstname' => (string) ($row['firstname'] ?? ''),
|
||||
'lastname' => (string) ($row['lastname'] ?? ''),
|
||||
'email' => (string) ($row['email'] ?? ''),
|
||||
'account_id' => (string) ($row['account_id'] ?? ''),
|
||||
'roles' => array_values(array_filter($roles)),
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user