diff --git a/app/Http/Controllers/Api/Administrator/AdministratorAbsenceController.php b/app/Http/Controllers/Api/Administrator/AdministratorAbsenceController.php index 547b31e1..498033ad 100644 --- a/app/Http/Controllers/Api/Administrator/AdministratorAbsenceController.php +++ b/app/Http/Controllers/Api/Administrator/AdministratorAbsenceController.php @@ -3,10 +3,11 @@ namespace App\Http\Controllers\Api\Administrator; use App\Http\Controllers\Controller; -use App\Http\Requests\Administrator\SubmitAdministratorAbsenceRequest; use App\Services\Administrator\AdministratorAbsenceService; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Validator; class AdministratorAbsenceController extends Controller { @@ -25,13 +26,42 @@ class AdministratorAbsenceController extends Controller return response()->json($this->service->getAbsenceFormData($userId)); } - public function store(SubmitAdministratorAbsenceRequest $request): JsonResponse + public function store(Request $request): JsonResponse { $userId = (int) Auth::id(); if ($userId <= 0) { return response()->json(['message' => 'Please log in first.'], 401); } + $payload = $request->all(); + if (!array_key_exists('dates', $payload) || !array_key_exists('reason', $payload)) { + $json = json_decode($request->getContent() ?: '', true); + if (is_array($json)) { + $payload = array_merge($payload, $json); + } + } + + $validator = Validator::make($payload, [ + 'dates' => ['required', 'array', 'min:1'], + 'dates.*' => ['required', 'date_format:Y-m-d'], + 'reason_type' => ['nullable', 'string', 'max:100'], + 'reason' => ['required', 'string', 'max:2000'], + ], [ + 'dates.required' => 'At least one date is required.', + 'dates.array' => 'Dates must be submitted as an array.', + 'dates.min' => 'At least one date is required.', + 'dates.*.date_format' => 'Each date must be in Y-m-d format.', + 'reason.required' => 'Reason is required.', + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $request->merge($validator->validated()); $result = $this->service->submit($request, $userId); return response()->json([ diff --git a/app/Http/Controllers/Api/Administrator/AdministratorEnrollmentController.php b/app/Http/Controllers/Api/Administrator/AdministratorEnrollmentController.php index 6c961343..2bc753e6 100644 --- a/app/Http/Controllers/Api/Administrator/AdministratorEnrollmentController.php +++ b/app/Http/Controllers/Api/Administrator/AdministratorEnrollmentController.php @@ -3,11 +3,12 @@ namespace App\Http\Controllers\Api\Administrator; use App\Http\Controllers\Controller; -use App\Http\Requests\Administrator\UpdateEnrollmentStatusesRequest; use App\Services\Administrator\AdministratorEnrollmentService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\Rule; class AdministratorEnrollmentController extends Controller { @@ -30,14 +31,53 @@ class AdministratorEnrollmentController extends Controller return response()->json($this->service->showNewStudentsData()); } - public function updateStatuses(UpdateEnrollmentStatusesRequest $request): JsonResponse + public function updateStatuses(Request $request): JsonResponse { $editorUserId = (int) Auth::id(); if ($editorUserId <= 0) { return response()->json(['message' => 'Please log in first.'], 401); } - $data = $request->validated(); + $payload = $request->all(); + if (!array_key_exists('enrollment_status', $payload)) { + $json = json_decode($request->getContent() ?: '', true); + if (is_array($json) && array_key_exists('enrollment_status', $json)) { + $payload['enrollment_status'] = $json['enrollment_status']; + } + } + if (!array_key_exists('enrollment_status', $payload)) { + $payload['enrollment_status'] = $request->query('enrollment_status'); + } + + $allowedStatuses = [ + 'admission under review', + 'payment pending', + 'enrolled', + 'withdraw under review', + 'refund pending', + 'withdrawn', + 'denied', + 'waitlist', + ]; + + $validator = Validator::make($payload, [ + 'enrollment_status' => ['required', 'array', 'min:1'], + 'enrollment_status.*' => ['required', 'string', Rule::in($allowedStatuses)], + ], [ + 'enrollment_status.required' => 'Enrollment statuses are required.', + 'enrollment_status.array' => 'Enrollment statuses must be an array.', + 'enrollment_status.min' => 'At least one enrollment status is required.', + 'enrollment_status.*.in' => 'One or more enrollment statuses are invalid.', + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $data = $validator->validated(); $result = $this->service->updateStatuses( (array) ($data['enrollment_status'] ?? []), $editorUserId diff --git a/app/Http/Controllers/Api/Administrator/AdministratorNotificationController.php b/app/Http/Controllers/Api/Administrator/AdministratorNotificationController.php index 354b63a0..efc793ff 100644 --- a/app/Http/Controllers/Api/Administrator/AdministratorNotificationController.php +++ b/app/Http/Controllers/Api/Administrator/AdministratorNotificationController.php @@ -3,10 +3,10 @@ namespace App\Http\Controllers\Api\Administrator; use App\Http\Controllers\Controller; -use App\Http\Requests\Administrator\SaveAdminNotificationSubjectsRequest; -use App\Http\Requests\Administrator\SavePrintRecipientsRequest; use App\Services\Administrator\AdministratorNotificationService; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; class AdministratorNotificationController extends Controller { @@ -20,9 +20,31 @@ class AdministratorNotificationController extends Controller return response()->json($this->service->notificationsAlertsData()); } - public function saveAlerts(SaveAdminNotificationSubjectsRequest $request): JsonResponse + public function saveAlerts(Request $request): JsonResponse { - $data = $request->validated(); + $payload = $request->all(); + if (!array_key_exists('subjects', $payload)) { + $json = json_decode($request->getContent() ?: '', true); + if (is_array($json)) { + $payload = array_merge($payload, $json); + } + } + + $validator = Validator::make($payload, [ + 'subjects' => ['required', 'array'], + ], [ + 'subjects.required' => 'Subjects payload is required.', + 'subjects.array' => 'Subjects payload must be an array.', + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $data = $validator->validated(); $result = $this->service->saveNotificationSubjects( (array) ($data['subjects'] ?? []) ); @@ -35,9 +57,31 @@ class AdministratorNotificationController extends Controller return response()->json($this->service->printNotificationRecipientsData()); } - public function savePrintRecipients(SavePrintRecipientsRequest $request): JsonResponse + public function savePrintRecipients(Request $request): JsonResponse { - $data = $request->validated(); + $payload = $request->all(); + if (!array_key_exists('notify', $payload)) { + $json = json_decode($request->getContent() ?: '', true); + if (is_array($json)) { + $payload = array_merge($payload, $json); + } + } + + $validator = Validator::make($payload, [ + 'notify' => ['required', 'array'], + ], [ + 'notify.required' => 'Notify payload is required.', + 'notify.array' => 'Notify payload must be an array.', + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $data = $validator->validated(); $result = $this->service->savePrintNotificationRecipients( (array) ($data['notify'] ?? []) ); diff --git a/app/Http/Controllers/Api/Administrator/AdministratorTeacherSubmissionController.php b/app/Http/Controllers/Api/Administrator/AdministratorTeacherSubmissionController.php index c4930ca1..0d7f66fa 100644 --- a/app/Http/Controllers/Api/Administrator/AdministratorTeacherSubmissionController.php +++ b/app/Http/Controllers/Api/Administrator/AdministratorTeacherSubmissionController.php @@ -3,10 +3,11 @@ namespace App\Http\Controllers\Api\Administrator; use App\Http\Controllers\Controller; -use App\Http\Requests\Administrator\SendTeacherSubmissionNotificationsRequest; use App\Services\Administrator\AdministratorTeacherSubmissionService; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Validator; class AdministratorTeacherSubmissionController extends Controller { @@ -20,13 +21,30 @@ class AdministratorTeacherSubmissionController extends Controller return response()->json($this->service->report()); } - public function notify(SendTeacherSubmissionNotificationsRequest $request): JsonResponse + public function notify(Request $request): JsonResponse { $adminId = (int) Auth::id(); if ($adminId <= 0) { return response()->json(['message' => 'Please log in first.'], 401); } + $validator = Validator::make($request->all(), [ + 'notify' => ['required', 'array', 'min:1'], + 'missing_items' => ['nullable', 'array'], + ], [ + 'notify.required' => 'Select at least one teacher to notify.', + 'notify.array' => 'Notify payload must be an array.', + 'notify.min' => 'Select at least one teacher to notify.', + 'missing_items.array' => 'Missing items payload must be an array.', + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + $result = $this->service->sendNotifications($request, $adminId); return response()->json([ diff --git a/app/Http/Controllers/Api/Assignment/AssignmentApiController.php b/app/Http/Controllers/Api/Assignment/AssignmentApiController.php index 30911426..ca484af7 100644 --- a/app/Http/Controllers/Api/Assignment/AssignmentApiController.php +++ b/app/Http/Controllers/Api/Assignment/AssignmentApiController.php @@ -3,12 +3,12 @@ namespace App\Http\Controllers\Api\Assignment; use App\Http\Controllers\Controller; -use App\Http\Requests\Assignment\StoreAssignmentRequest; use App\Http\Resources\Assignment\AssignmentOverviewResource; use App\Http\Resources\Assignment\AssignmentSectionResource; use App\Services\Assignment\AssignmentService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; class AssignmentApiController extends Controller { @@ -29,10 +29,25 @@ class AssignmentApiController extends Controller ]); } - public function store(StoreAssignmentRequest $request): JsonResponse + public function store(Request $request): JsonResponse { + $validator = Validator::make($request->all(), [ + 'student_id' => ['required', 'integer', 'exists:students,id'], + 'class_section_id' => ['required', 'integer', 'exists:classSection,class_section_id'], + 'semester' => ['required', 'string', 'max:50'], + 'school_year' => ['required', 'string', 'max:50'], + 'description' => ['nullable', 'string'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + $assignment = $this->assignmentService->storeAssignment( - data: $request->validated(), + data: $validator->validated(), updatedBy: $request->user()?->id ); diff --git a/app/Http/Controllers/Api/Attendance/AttendanceCommentTemplateController.php b/app/Http/Controllers/Api/Attendance/AttendanceCommentTemplateController.php index af205b26..57cbb4d5 100644 --- a/app/Http/Controllers/Api/Attendance/AttendanceCommentTemplateController.php +++ b/app/Http/Controllers/Api/Attendance/AttendanceCommentTemplateController.php @@ -3,11 +3,10 @@ namespace App\Http\Controllers\Api\Attendance; use App\Http\Controllers\Controller; -use App\Http\Requests\AttendanceCommentTemplate\StoreAttendanceCommentTemplateRequest; -use App\Http\Requests\AttendanceCommentTemplate\UpdateAttendanceCommentTemplateRequest; use App\Services\AttendanceCommentTemplateService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; class AttendanceCommentTemplateController extends Controller { @@ -49,9 +48,28 @@ class AttendanceCommentTemplateController extends Controller ]); } - public function store(StoreAttendanceCommentTemplateRequest $request): JsonResponse + public function store(Request $request): JsonResponse { - $created = $this->service->create($request->validated()); + $validator = Validator::make($request->all(), [ + 'min_score' => ['required', 'integer', 'min:0', 'max:100'], + 'max_score' => ['required', 'integer', 'min:0', 'max:100', 'gte:min_score'], + 'template_text' => ['required', 'string', 'max:5000'], + 'is_active' => ['sometimes', 'boolean'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $data = $validator->validated(); + if (!array_key_exists('is_active', $data)) { + $data['is_active'] = true; + } + + $created = $this->service->create($data); return response()->json([ 'status' => 'success', @@ -60,9 +78,31 @@ class AttendanceCommentTemplateController extends Controller ], 201); } - public function update(UpdateAttendanceCommentTemplateRequest $request, int $id): JsonResponse + public function update(Request $request, int $id): JsonResponse { - $updated = $this->service->update($id, $request->validated()); + $validator = Validator::make($request->all(), [ + 'min_score' => ['sometimes', 'integer', 'min:0', 'max:100'], + 'max_score' => ['sometimes', 'integer', 'min:0', 'max:100'], + 'template_text' => ['sometimes', 'string', 'max:5000'], + 'is_active' => ['sometimes', 'boolean'], + ]); + + $validator->after(function ($validator) use ($request) { + $min = $request->has('min_score') ? (int) $request->input('min_score') : null; + $max = $request->has('max_score') ? (int) $request->input('max_score') : null; + if ($min !== null && $max !== null && $max < $min) { + $validator->errors()->add('max_score', 'The max_score field must be greater than or equal to min_score.'); + } + }); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $updated = $this->service->update($id, $validator->validated()); return response()->json([ 'status' => 'success', diff --git a/app/Http/Controllers/Api/AttendanceTracking/AttendanceTrackingController.php b/app/Http/Controllers/Api/AttendanceTracking/AttendanceTrackingController.php index bdb79f31..6d53db9f 100644 --- a/app/Http/Controllers/Api/AttendanceTracking/AttendanceTrackingController.php +++ b/app/Http/Controllers/Api/AttendanceTracking/AttendanceTrackingController.php @@ -3,14 +3,10 @@ namespace App\Http\Controllers\Api\AttendanceTracking; use App\Http\Controllers\Controller; -use App\Http\Requests\AttendanceTracking\ComposeAttendanceEmailRequest; -use App\Http\Requests\AttendanceTracking\ParentsInfoRequest; -use App\Http\Requests\AttendanceTracking\RecordAttendanceTrackingRequest; -use App\Http\Requests\AttendanceTracking\SaveAttendanceNotificationNoteRequest; -use App\Http\Requests\AttendanceTracking\SendAttendanceManualEmailRequest; use App\Services\AttendanceTracking\AttendanceTrackingService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; class AttendanceTrackingController extends Controller { @@ -63,9 +59,26 @@ class AttendanceTrackingController extends Controller ); } - public function record(RecordAttendanceTrackingRequest $request): JsonResponse + public function record(Request $request): JsonResponse { - $result = $this->service->record($request->validated()); + $validator = Validator::make($request->all(), [ + 'student_id' => ['required', 'integer', 'min:1'], + 'date' => ['required', 'date_format:Y-m-d'], + 'parent_email' => ['nullable', 'email'], + 'parent_name' => ['nullable', 'string'], + 'subject_type' => ['nullable', 'string'], + 'semester' => ['nullable', 'string'], + 'school_year' => ['nullable', 'string'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $result = $this->service->record($validator->validated()); return response()->json( $result, @@ -80,13 +93,28 @@ class AttendanceTrackingController extends Controller return response()->json($result); } - public function compose(ComposeAttendanceEmailRequest $request): JsonResponse + public function compose(Request $request): JsonResponse { + $validator = Validator::make($request->query(), [ + 'student_id' => ['required', 'integer', 'min:1'], + 'code' => ['nullable', 'string'], + 'variant' => ['nullable', 'string'], + 'incident_date' => ['nullable', 'date_format:Y-m-d'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $data = $validator->validated(); $result = $this->service->compose( - (int) $request->validated('student_id'), - (string) $request->validated('code', 'ABS_1'), - (string) $request->validated('variant', 'default'), - $request->validated('incident_date'), + (int) ($data['student_id'] ?? 0), + (string) ($data['code'] ?? 'ABS_1'), + (string) ($data['variant'] ?? 'default'), + $data['incident_date'] ?? null, ); return response()->json( @@ -95,9 +123,26 @@ class AttendanceTrackingController extends Controller ); } - public function sendManualEmail(SendAttendanceManualEmailRequest $request): JsonResponse + public function sendManualEmail(Request $request): JsonResponse { - $result = $this->service->sendEmailManual($request->validated()); + $validator = Validator::make($request->all(), [ + 'student_id' => ['required', 'integer', 'min:1'], + 'to' => ['required', 'email'], + 'subject' => ['required', 'string'], + 'body_html' => ['required', 'string'], + 'code' => ['required', 'string'], + 'variant' => ['nullable', 'string'], + 'incident_date' => ['nullable', 'date_format:Y-m-d'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $result = $this->service->sendEmailManual($validator->validated()); return response()->json( $result, @@ -105,9 +150,21 @@ class AttendanceTrackingController extends Controller ); } - public function parentsInfo(ParentsInfoRequest $request): JsonResponse + public function parentsInfo(Request $request): JsonResponse { - $result = $this->service->parentsInfo((int) $request->validated('student_id')); + $validator = Validator::make($request->query(), [ + 'student_id' => ['required', 'integer', 'min:1'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $data = $validator->validated(); + $result = $this->service->parentsInfo((int) ($data['student_id'] ?? 0)); return response()->json( $result, @@ -115,9 +172,26 @@ class AttendanceTrackingController extends Controller ); } - public function saveNotificationNote(SaveAttendanceNotificationNoteRequest $request): JsonResponse + public function saveNotificationNote(Request $request): JsonResponse { - $result = $this->service->saveNotificationNote($request->validated()); + $validator = Validator::make($request->all(), [ + 'student_id' => ['required', 'integer', 'min:1'], + 'code' => ['required', 'string'], + 'note' => ['required', 'string'], + 'incident_date' => ['nullable', 'date_format:Y-m-d'], + 'to' => ['nullable', 'email'], + 'subject' => ['nullable', 'string'], + 'variant' => ['nullable', 'string'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $result = $this->service->saveNotificationNote($validator->validated()); return response()->json( $result, diff --git a/app/Http/Controllers/Api/Auth/RegisterController.php b/app/Http/Controllers/Api/Auth/RegisterController.php index 5ec3221e..d64cfb91 100644 --- a/app/Http/Controllers/Api/Auth/RegisterController.php +++ b/app/Http/Controllers/Api/Auth/RegisterController.php @@ -3,11 +3,12 @@ namespace App\Http\Controllers\Api\Auth; use App\Http\Controllers\Api\BaseApiController; -use App\Http\Requests\Auth\RegisterRequest; use App\Http\Resources\Auth\RegisterResource; use App\Services\Auth\RegistrationCaptchaService; use App\Services\Auth\RegistrationService; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; class RegisterController extends BaseApiController { @@ -28,7 +29,7 @@ class RegisterController extends BaseApiController ]); } - public function store(RegisterRequest $request): JsonResponse + public function store(Request $request): JsonResponse { if (app()->runningUnitTests() && $request->header('X-Debug-Request') === '1') { return response()->json([ @@ -43,7 +44,15 @@ class RegisterController extends BaseApiController ]); } - $result = $this->service->register($request->validated()); + $validator = Validator::make($request->all(), \App\Http\Requests\Auth\RegisterRequest::ruleset()); + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $result = $this->service->register($validator->validated()); if (empty($result['ok'])) { $code = $result['code'] ?? 'registration_failed'; diff --git a/app/Http/Controllers/Api/Badges/BadgeController.php b/app/Http/Controllers/Api/Badges/BadgeController.php index 6637a79a..9c253950 100644 --- a/app/Http/Controllers/Api/Badges/BadgeController.php +++ b/app/Http/Controllers/Api/Badges/BadgeController.php @@ -3,13 +3,12 @@ namespace App\Http\Controllers\Api\Badges; use App\Http\Controllers\Controller; -use App\Http\Requests\Badges\BadgePrintStatusRequest; -use App\Http\Requests\Badges\GenerateBadgePdfRequest; -use App\Http\Requests\Badges\LogBadgePrintRequest; use App\Services\Badges\BadgeFormDataService; use App\Services\Badges\BadgePdfService; use App\Services\Badges\BadgePrintLogService; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; use Throwable; class BadgeController extends Controller @@ -21,24 +20,41 @@ class BadgeController extends Controller ) { } - public function generatePdf(GenerateBadgePdfRequest $request) + public function generatePdf(Request $request) { + $validator = Validator::make($request->all(), [ + 'user_ids' => ['required', 'array', 'min:1'], + 'user_ids.*' => ['integer', 'min:1'], + 'school_year' => ['nullable', 'string', 'max:50'], + 'roles' => ['nullable', 'array'], + 'classes' => ['nullable', 'array'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $data = $validator->validated(); + return $this->badgePdfService->generate( - userIds: $request->input('user_ids', []), - schoolYear: $request->input('school_year'), - rolesMap: $request->input('roles', []), - classesMap: $request->input('classes', []), + userIds: $data['user_ids'] ?? [], + schoolYear: $data['school_year'] ?? null, + rolesMap: $data['roles'] ?? [], + classesMap: $data['classes'] ?? [], actorId: optional($request->user())->id ); } - public function printStatus(BadgePrintStatusRequest $request): JsonResponse + public function printStatus(Request $request): JsonResponse { try { return response()->json([ 'ok' => true, 'data' => $this->badgePrintLogService->getStatus( - $request->normalizedUserIds(), + $this->parseUserIds($request), $request->input('school_year') ), ]); @@ -50,15 +66,32 @@ class BadgeController extends Controller } } - public function logPrint(LogBadgePrintRequest $request): JsonResponse + public function logPrint(Request $request): JsonResponse { try { + $validator = Validator::make($request->all(), [ + 'user_ids' => ['required', 'array', 'min:1'], + 'user_ids.*' => ['integer', 'min:1'], + 'school_year' => ['nullable', 'string', 'max:50'], + 'roles' => ['nullable', 'array'], + 'classes' => ['nullable', 'array'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $data = $validator->validated(); + $inserted = $this->badgePrintLogService->log( - userIds: $request->input('user_ids', []), + userIds: $data['user_ids'] ?? [], actorId: optional($request->user())->id, - schoolYear: $request->input('school_year'), - rolesMap: $request->input('roles', []), - classesMap: $request->input('classes', []) + schoolYear: $data['school_year'] ?? null, + rolesMap: $data['roles'] ?? [], + classesMap: $data['classes'] ?? [] ); return response()->json([ @@ -73,15 +106,30 @@ class BadgeController extends Controller } } - public function formData(BadgePrintStatusRequest $request): JsonResponse + public function formData(Request $request): JsonResponse { return response()->json([ 'ok' => true, 'data' => $this->badgeFormDataService->build( schoolYear: $request->input('school_year'), - selectedUserIds: $request->normalizedUserIds(), + selectedUserIds: $this->parseUserIds($request), activeRole: $request->input('active_role') ), ]); } -} \ No newline at end of file + + private function parseUserIds(Request $request): array + { + $userIds = $request->input('user_ids', $request->query('user_ids', [])); + + if (is_string($userIds)) { + $userIds = array_filter(array_map('trim', explode(',', $userIds)), 'strlen'); + } elseif (!is_array($userIds)) { + $userIds = $userIds ? [$userIds] : []; + } + + $userIds = array_values(array_unique(array_map(static fn ($v) => (int) $v, $userIds))); + + return array_values(array_filter($userIds, static fn ($v) => $v > 0)); + } +} diff --git a/app/Http/Controllers/Api/Finance/FeeCalculationController.php b/app/Http/Controllers/Api/Finance/FeeCalculationController.php index 29cca4d0..dd7dbf70 100644 --- a/app/Http/Controllers/Api/Finance/FeeCalculationController.php +++ b/app/Http/Controllers/Api/Finance/FeeCalculationController.php @@ -3,13 +3,13 @@ namespace App\Http\Controllers\Api\Finance; use App\Http\Controllers\Api\BaseApiController; -use App\Http\Requests\Fees\FeeRefundRequest; -use App\Http\Requests\Fees\FeeTuitionTotalRequest; use App\Http\Resources\Fees\FeeRefundResource; use App\Http\Resources\Fees\FeeTuitionTotalResource; use App\Services\Fees\FeeRefundCalculatorService; use App\Services\Fees\FeeStudentFeeService; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; class FeeCalculationController extends BaseApiController { @@ -20,9 +20,26 @@ class FeeCalculationController extends BaseApiController parent::__construct(); } - public function refund(FeeRefundRequest $request): JsonResponse + public function refund(Request $request): JsonResponse { - $payload = $request->validated(); + $validator = Validator::make($request->all(), [ + 'parent_id' => ['required', 'integer', 'min:1'], + 'students' => ['required', 'array'], + 'students.*.student_id' => ['nullable', 'integer'], + 'students.*.class_section_id' => ['required', 'integer', 'min:1'], + 'students.*.enrollment_status' => ['required', 'string', 'max:50'], + 'students.*.admission_status' => ['required', 'string', 'max:50'], + 'students.*.withdrawal_date' => ['nullable', 'date'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $result = $this->refunds->calculateRefund($payload['students'], (int) $payload['parent_id']); return response()->json([ @@ -31,9 +48,22 @@ class FeeCalculationController extends BaseApiController ]); } - public function tuitionTotal(FeeTuitionTotalRequest $request): JsonResponse + public function tuitionTotal(Request $request): JsonResponse { - $payload = $request->validated(); + $validator = Validator::make($request->all(), [ + 'students' => ['required', 'array'], + 'students.*.class_section_id' => ['required', 'integer', 'min:1'], + 'students.*.grade' => ['nullable', 'string', 'max:20'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $total = $this->tuition->totalTuitionFee($payload['students']); return response()->json([ diff --git a/app/Http/Controllers/Api/Finance/InvoiceController.php b/app/Http/Controllers/Api/Finance/InvoiceController.php index 944187af..be40d695 100644 --- a/app/Http/Controllers/Api/Finance/InvoiceController.php +++ b/app/Http/Controllers/Api/Finance/InvoiceController.php @@ -3,7 +3,6 @@ namespace App\Http\Controllers\Api\Finance; use App\Http\Controllers\Api\BaseApiController; -use App\Http\Requests\Invoices\InvoiceGenerateRequest; use App\Http\Requests\Invoices\InvoiceManagementRequest; use App\Http\Requests\Invoices\InvoiceParentRequest; use App\Http\Requests\Invoices\InvoiceStatusRequest; @@ -15,6 +14,8 @@ use App\Services\Invoices\InvoiceManagementService; use App\Services\Invoices\InvoicePaymentService; use App\Services\Invoices\InvoicePdfService; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; use Symfony\Component\HttpFoundation\StreamedResponse; class InvoiceController extends BaseApiController @@ -44,9 +45,22 @@ class InvoiceController extends BaseApiController ]); } - public function generate(InvoiceGenerateRequest $request): JsonResponse + public function generate(Request $request): JsonResponse { - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'parent_id' => ['required', 'integer', 'exists:users,id'], + 'school_year' => ['nullable', 'string', 'max:20'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $result = $this->generation->generateInvoice((int) $payload['parent_id'], $payload['school_year'] ?? null); if (empty($result['ok'])) { diff --git a/app/Http/Controllers/Api/Finance/PaymentController.php b/app/Http/Controllers/Api/Finance/PaymentController.php index 8b044593..23008afb 100644 --- a/app/Http/Controllers/Api/Finance/PaymentController.php +++ b/app/Http/Controllers/Api/Finance/PaymentController.php @@ -4,13 +4,13 @@ namespace App\Http\Controllers\Api\Finance; use App\Http\Controllers\Api\BaseApiController; use App\Http\Requests\Payments\PaymentByParentRequest; -use App\Http\Requests\Payments\PaymentCreateRequest; -use App\Http\Requests\Payments\PaymentUpdateBalanceRequest; use App\Http\Resources\Payments\PaymentResource; use App\Services\Payments\PaymentBalanceService; use App\Services\Payments\PaymentLookupService; use App\Services\Payments\PaymentPlanService; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; class PaymentController extends BaseApiController { @@ -22,9 +22,30 @@ class PaymentController extends BaseApiController parent::__construct(); } - public function store(PaymentCreateRequest $request): JsonResponse + public function store(Request $request): JsonResponse { - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'parent_id' => ['required', 'integer', 'exists:users,id'], + 'invoice_id' => ['nullable', 'integer', 'exists:invoices,id'], + 'total_amount' => ['required', 'numeric', 'min:0.01'], + 'number_of_installments' => ['required', 'integer', 'min:1'], + 'payment_date' => ['nullable', 'date'], + 'payment_method' => ['nullable', 'string', 'max:50'], + 'payment_type' => ['nullable', 'string', 'max:50'], + 'status' => ['nullable', 'string', 'max:50'], + 'semester' => ['nullable', 'string', 'max:50'], + 'school_year' => ['nullable', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $payment = $this->planService->createPlan($payload); return response()->json([ @@ -44,9 +65,21 @@ class PaymentController extends BaseApiController ]); } - public function updateBalance(PaymentUpdateBalanceRequest $request, int $paymentId): JsonResponse + public function updateBalance(Request $request, int $paymentId): JsonResponse { - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'paid_amount' => ['required', 'numeric', 'min:0.01'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $updated = $this->balanceService->updateBalance($paymentId, (float) $payload['paid_amount']); if (!$updated) { diff --git a/app/Http/Controllers/Api/Finance/PaymentEventChargesController.php b/app/Http/Controllers/Api/Finance/PaymentEventChargesController.php index fa6dafd9..eef854e7 100644 --- a/app/Http/Controllers/Api/Finance/PaymentEventChargesController.php +++ b/app/Http/Controllers/Api/Finance/PaymentEventChargesController.php @@ -4,9 +4,10 @@ namespace App\Http\Controllers\Api\Finance; use App\Http\Controllers\Api\BaseApiController; use App\Http\Requests\Payments\PaymentEventChargesListRequest; -use App\Http\Requests\Payments\PaymentEventChargesStoreRequest; use App\Services\Payments\PaymentEventChargesService; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; class PaymentEventChargesController extends BaseApiController { @@ -23,9 +24,29 @@ class PaymentEventChargesController extends BaseApiController return response()->json(['ok' => true] + $data); } - public function store(PaymentEventChargesStoreRequest $request): JsonResponse + public function store(Request $request): JsonResponse { - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'parent_id' => ['required', 'integer', 'min:1'], + 'event_name' => ['required', 'string', 'max:255'], + 'description' => ['nullable', 'string', 'max:1000'], + 'amount' => ['required', 'numeric', 'min:0'], + 'semester' => ['required', 'string', 'max:50'], + 'school_year' => ['required', 'string', 'max:50'], + 'student_ids' => ['nullable', 'array'], + 'student_ids.*' => ['integer', 'min:1'], + 'student_id' => ['nullable', 'integer', 'min:1'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $userId = (int) (auth()->id() ?? 0); $count = $this->service->addCharges($payload, $userId); diff --git a/app/Http/Controllers/Api/Finance/PaymentManualController.php b/app/Http/Controllers/Api/Finance/PaymentManualController.php index 5cd5f466..a25a7c7c 100644 --- a/app/Http/Controllers/Api/Finance/PaymentManualController.php +++ b/app/Http/Controllers/Api/Finance/PaymentManualController.php @@ -4,12 +4,11 @@ namespace App\Http\Controllers\Api\Finance; use App\Http\Controllers\Api\BaseApiController; use App\Http\Requests\Payments\PaymentManualEditRequest; -use App\Http\Requests\Payments\PaymentManualSearchRequest; -use App\Http\Requests\Payments\PaymentManualSuggestRequest; use App\Http\Requests\Payments\PaymentManualUpdateRequest; use App\Services\Payments\PaymentManualService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; class PaymentManualController extends BaseApiController { @@ -18,9 +17,22 @@ class PaymentManualController extends BaseApiController parent::__construct(); } - public function search(PaymentManualSearchRequest $request): JsonResponse + public function search(Request $request): JsonResponse { - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'search_term' => ['nullable', 'string', 'max:255'], + 'school_year' => ['nullable', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $data = $this->service->search( (string) ($payload['search_term'] ?? ''), $payload['school_year'] ?? null @@ -29,9 +41,21 @@ class PaymentManualController extends BaseApiController return response()->json(['ok' => true] + $data); } - public function suggest(PaymentManualSuggestRequest $request): JsonResponse + public function suggest(Request $request): JsonResponse { - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'q' => ['required', 'string', 'max:255'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $items = $this->service->suggest((string) ($payload['q'] ?? '')); return response()->json(['ok' => true, 'items' => $items]); diff --git a/app/Http/Controllers/Api/Finance/PaymentTransactionController.php b/app/Http/Controllers/Api/Finance/PaymentTransactionController.php index 04d66eb7..cfec0534 100644 --- a/app/Http/Controllers/Api/Finance/PaymentTransactionController.php +++ b/app/Http/Controllers/Api/Finance/PaymentTransactionController.php @@ -3,11 +3,11 @@ namespace App\Http\Controllers\Api\Finance; use App\Http\Controllers\Api\BaseApiController; -use App\Http\Requests\Payments\PaymentTransactionCreateRequest; use App\Http\Resources\Payments\PaymentTransactionResource; use App\Services\Payments\PaymentTransactionService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; class PaymentTransactionController extends BaseApiController { @@ -16,9 +16,31 @@ class PaymentTransactionController extends BaseApiController parent::__construct(); } - public function store(PaymentTransactionCreateRequest $request): JsonResponse + public function store(Request $request): JsonResponse { - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'transaction_id' => ['required', 'string', 'max:100'], + 'payment_id' => ['required', 'integer', 'min:1'], + 'transaction_date' => ['nullable', 'date'], + 'amount' => ['required', 'numeric', 'min:0.01'], + 'payment_method' => ['required', 'string', 'max:50'], + 'payment_status' => ['nullable', 'string', 'max:50'], + 'transaction_fee' => ['nullable', 'numeric', 'min:0'], + 'payment_reference' => ['nullable', 'string', 'max:100'], + 'is_full_payment' => ['nullable', 'boolean'], + 'school_year' => ['nullable', 'string', 'max:50'], + 'semester' => ['nullable', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $transaction = $this->service->create($payload); return response()->json([ diff --git a/app/Http/Controllers/Api/Finance/PurchaseOrderController.php b/app/Http/Controllers/Api/Finance/PurchaseOrderController.php index bc40ed91..ea9ccf4a 100644 --- a/app/Http/Controllers/Api/Finance/PurchaseOrderController.php +++ b/app/Http/Controllers/Api/Finance/PurchaseOrderController.php @@ -4,8 +4,6 @@ namespace App\Http\Controllers\Api\Finance; use App\Http\Controllers\Api\BaseApiController; use App\Http\Requests\PurchaseOrders\PurchaseOrderIndexRequest; -use App\Http\Requests\PurchaseOrders\PurchaseOrderReceiveRequest; -use App\Http\Requests\PurchaseOrders\PurchaseOrderStoreRequest; use App\Http\Resources\PurchaseOrders\PurchaseOrderItemResource; use App\Http\Resources\PurchaseOrders\PurchaseOrderResource; use App\Models\PurchaseOrder; @@ -14,6 +12,8 @@ use App\Services\PurchaseOrders\PurchaseOrderQueryService; use App\Services\PurchaseOrders\PurchaseOrderReceiveService; use App\Services\PurchaseOrders\PurchaseOrderStatusService; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; use RuntimeException; class PurchaseOrderController extends BaseApiController @@ -63,9 +63,41 @@ class PurchaseOrderController extends BaseApiController ]); } - public function store(PurchaseOrderStoreRequest $request): JsonResponse + public function store(Request $request): JsonResponse { - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'po_number' => ['required', 'string', 'max:60'], + 'supplier_id' => ['required', 'integer', 'min:1', 'exists:suppliers,id'], + 'status' => ['nullable', 'string', 'in:' . implode(',', PurchaseOrder::allowedStatuses())], + 'order_date' => ['nullable', 'date'], + 'expected_date' => ['nullable', 'date'], + 'notes' => ['nullable', 'string', 'max:5000'], + 'items' => ['required', 'array', 'min:1'], + 'items.*.supply_id' => ['required', 'integer', 'min:1', 'exists:supplies,id'], + 'items.*.description' => ['nullable', 'string', 'max:1000'], + 'items.*.quantity' => ['required', 'integer', 'min:1'], + 'items.*.unit_cost' => ['required', 'numeric', 'min:0'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); + if (!empty($payload['order_date']) && !empty($payload['expected_date'])) { + $orderTs = strtotime((string) $payload['order_date']); + $expectedTs = strtotime((string) $payload['expected_date']); + if ($orderTs !== false && $expectedTs !== false && $expectedTs < $orderTs) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => ['expected_date' => ['The expected date must be on or after the order date.']], + ], 422); + } + } try { $po = $this->createService->create($payload); @@ -81,9 +113,23 @@ class PurchaseOrderController extends BaseApiController ], 201); } - public function receive(PurchaseOrderReceiveRequest $request, int $id): JsonResponse + public function receive(Request $request, int $id): JsonResponse { - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'items' => ['required', 'array', 'min:1'], + 'items.*.id' => ['required', 'integer', 'min:1'], + 'items.*.quantity' => ['required', 'integer', 'min:1'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $po = PurchaseOrder::query()->find($id); if (!$po) { diff --git a/app/Http/Controllers/Api/Finance/ReimbursementController.php b/app/Http/Controllers/Api/Finance/ReimbursementController.php index 4a173cb6..8504cead 100644 --- a/app/Http/Controllers/Api/Finance/ReimbursementController.php +++ b/app/Http/Controllers/Api/Finance/ReimbursementController.php @@ -6,17 +6,13 @@ use App\Http\Controllers\Api\BaseApiController; use App\Http\Requests\Files\FileNameRequest; use App\Http\Requests\Reimbursements\ReimbursementBatchAdminFileRequest; use App\Http\Requests\Reimbursements\ReimbursementBatchAssignmentRequest; -use App\Http\Requests\Reimbursements\ReimbursementBatchCreateRequest; use App\Http\Requests\Reimbursements\ReimbursementBatchEmailRequest; use App\Http\Requests\Reimbursements\ReimbursementBatchLockRequest; use App\Http\Requests\Reimbursements\ReimbursementExportBatchRequest; use App\Http\Requests\Reimbursements\ReimbursementExportRequest; use App\Http\Requests\Reimbursements\ReimbursementIndexRequest; -use App\Http\Requests\Reimbursements\ReimbursementMarkDonationRequest; use App\Http\Requests\Reimbursements\ReimbursementProcessRequest; -use App\Http\Requests\Reimbursements\ReimbursementStoreRequest; use App\Http\Requests\Reimbursements\ReimbursementUnderProcessingRequest; -use App\Http\Requests\Reimbursements\ReimbursementUpdateRequest; use App\Http\Resources\Reimbursements\ReimbursementBatchResource; use App\Http\Resources\Reimbursements\ReimbursementExpenseResource; use App\Http\Resources\Reimbursements\ReimbursementRecipientResource; @@ -37,6 +33,7 @@ use App\Services\Reimbursements\ReimbursementQueryService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; +use Illuminate\Support\Facades\Validator; use RuntimeException; class ReimbursementController extends BaseApiController @@ -69,9 +66,21 @@ class ReimbursementController extends BaseApiController ]); } - public function markDonation(ReimbursementMarkDonationRequest $request): JsonResponse + public function markDonation(Request $request): JsonResponse { - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'expense_id' => ['required', 'integer', 'min:1'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $userId = (int) (auth()->id() ?? 0); try { @@ -87,9 +96,21 @@ class ReimbursementController extends BaseApiController return response()->json(['ok' => true]); } - public function createBatch(ReimbursementBatchCreateRequest $request): JsonResponse + public function createBatch(Request $request): JsonResponse { - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'title' => ['nullable', 'string', 'max:255'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $userId = (int) (auth()->id() ?? 0); $schoolYear = $this->context->getSchoolYear(); $semester = $this->context->getSemester(); @@ -289,9 +310,29 @@ class ReimbursementController extends BaseApiController ]); } - public function store(ReimbursementStoreRequest $request): JsonResponse + public function store(Request $request): JsonResponse { - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'amount' => ['required', 'numeric', 'gt:0'], + 'reimbursed_to' => ['required', 'integer', 'min:1'], + 'reimbursement_method' => ['required', 'in:Cash,Check'], + 'check_number' => ['required_if:reimbursement_method,Check', 'nullable', 'string', 'max:50'], + 'receipt' => ['required_if:reimbursement_method,Check', 'nullable', 'file', 'max:2048', 'mimes:jpg,jpeg,png,webp,gif,pdf'], + 'expense_id' => ['nullable', 'integer', 'min:1'], + 'description' => ['nullable', 'string'], + 'school_year' => ['nullable', 'string', 'max:20'], + 'semester' => ['nullable', 'string', 'max:20'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $userId = (int) (auth()->id() ?? 0); $receiptName = null; @@ -361,14 +402,32 @@ class ReimbursementController extends BaseApiController ]); } - public function update(ReimbursementUpdateRequest $request, int $id): JsonResponse + public function update(Request $request, int $id): JsonResponse { $reimb = Reimbursement::query()->find($id); if (!$reimb) { return response()->json(['ok' => false, 'message' => 'Reimbursement not found.'], 404); } - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'amount' => ['required', 'numeric', 'gt:0'], + 'reimbursed_to' => ['required', 'integer', 'min:1'], + 'reimbursement_method' => ['required', 'in:Cash,Check'], + 'check_number' => ['required_if:reimbursement_method,Check', 'nullable', 'string', 'max:50'], + 'receipt' => ['nullable', 'file', 'max:2048', 'mimes:jpg,jpeg,png,webp,gif,pdf'], + 'remove_receipt' => ['nullable', 'boolean'], + 'description' => ['nullable', 'string'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $userId = (int) (auth()->id() ?? 0); $receiptName = $reimb->receipt_path; diff --git a/app/Http/Controllers/Api/Incidents/IncidentController.php b/app/Http/Controllers/Api/Incidents/IncidentController.php index 1d30d344..54336162 100644 --- a/app/Http/Controllers/Api/Incidents/IncidentController.php +++ b/app/Http/Controllers/Api/Incidents/IncidentController.php @@ -7,7 +7,6 @@ use App\Http\Requests\Incidents\IncidentCancelRequest; use App\Http\Requests\Incidents\IncidentCloseRequest; use App\Http\Requests\Incidents\IncidentListRequest; use App\Http\Requests\Incidents\IncidentStateRequest; -use App\Http\Requests\Incidents\IncidentStoreRequest; use App\Http\Resources\Incidents\IncidentAnalysisStudentResource; use App\Http\Resources\Incidents\IncidentGradeResource; use App\Http\Resources\Incidents\IncidentResource; @@ -16,6 +15,8 @@ use App\Services\Incidents\CurrentIncidentService; use App\Services\Incidents\IncidentAnalysisService; use App\Services\Incidents\IncidentHistoryService; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; class IncidentController extends BaseApiController { @@ -83,9 +84,26 @@ class IncidentController extends BaseApiController ]); } - public function store(IncidentStoreRequest $request): JsonResponse + public function store(Request $request): JsonResponse { - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'student_id' => ['required', 'integer', 'min:1'], + 'grade' => ['required', 'integer', 'min:1'], + 'incident' => ['required', 'string', 'max:255'], + 'description' => ['required', 'string', 'max:2000'], + 'school_year' => ['nullable', 'string', 'max:20'], + 'semester' => ['nullable', 'string', 'max:20'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $result = $this->currentService->addIncident($payload, (int) (auth()->id() ?? 0)); if (empty($result['ok'])) { @@ -109,9 +127,22 @@ class IncidentController extends BaseApiController ]); } - public function close(IncidentCloseRequest $request, int $incidentId): JsonResponse + public function close(Request $request, int $incidentId): JsonResponse { - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'state_description' => ['required', 'string', 'max:2000'], + 'action_taken' => ['nullable', 'string', 'max:2000'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $result = $this->currentService->closeIncident( $incidentId, (string) $payload['state_description'], diff --git a/app/Http/Controllers/Api/Scores/FinalController.php b/app/Http/Controllers/Api/Scores/FinalController.php index 7ba0f37b..8555dc4e 100644 --- a/app/Http/Controllers/Api/Scores/FinalController.php +++ b/app/Http/Controllers/Api/Scores/FinalController.php @@ -3,13 +3,14 @@ namespace App\Http\Controllers\Api\Scores; use App\Http\Controllers\Api\BaseApiController; -use App\Http\Requests\Scores\ScoreClassRequest; use App\Http\Requests\Scores\ScoreUpdateRequest; use App\Http\Resources\Scores\ScoreStudentResource; use App\Models\Student; use App\Services\Scores\ExamScoreService; use App\Services\Scores\SemesterScoreService; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; class FinalController extends BaseApiController { @@ -20,9 +21,23 @@ class FinalController extends BaseApiController parent::__construct(); } - public function index(ScoreClassRequest $request): JsonResponse + public function index(Request $request): JsonResponse { - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'class_section_id' => ['required', 'integer', 'min:1'], + 'semester' => ['nullable', 'string', 'max:50'], + 'school_year' => ['nullable', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $data = $this->service->list('final_exam', (int) $payload['class_section_id'], $payload['semester'] ?? null, $payload['school_year'] ?? null); return response()->json([ diff --git a/app/Http/Controllers/Api/Scores/HomeworkController.php b/app/Http/Controllers/Api/Scores/HomeworkController.php index a78ab68f..6c2ead6d 100644 --- a/app/Http/Controllers/Api/Scores/HomeworkController.php +++ b/app/Http/Controllers/Api/Scores/HomeworkController.php @@ -4,13 +4,14 @@ namespace App\Http\Controllers\Api\Scores; use App\Http\Controllers\Api\BaseApiController; use App\Http\Requests\Scores\ScoreAddColumnRequest; -use App\Http\Requests\Scores\ScoreClassRequest; use App\Http\Requests\Scores\ScoreUpdateRequest; use App\Http\Resources\Scores\ScoreStudentResource; use App\Models\Student; use App\Services\Scores\HomeworkScoreService; use App\Services\Scores\SemesterScoreService; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; class HomeworkController extends BaseApiController { @@ -21,9 +22,23 @@ class HomeworkController extends BaseApiController parent::__construct(); } - public function index(ScoreClassRequest $request): JsonResponse + public function index(Request $request): JsonResponse { - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'class_section_id' => ['required', 'integer', 'min:1'], + 'semester' => ['nullable', 'string', 'max:50'], + 'school_year' => ['nullable', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $data = $this->service->list( (int) $payload['class_section_id'], $payload['semester'] ?? null, diff --git a/app/Http/Controllers/Api/Scores/MidtermController.php b/app/Http/Controllers/Api/Scores/MidtermController.php index 25e8d335..cf8bcde6 100644 --- a/app/Http/Controllers/Api/Scores/MidtermController.php +++ b/app/Http/Controllers/Api/Scores/MidtermController.php @@ -3,13 +3,14 @@ namespace App\Http\Controllers\Api\Scores; use App\Http\Controllers\Api\BaseApiController; -use App\Http\Requests\Scores\ScoreClassRequest; use App\Http\Requests\Scores\ScoreUpdateRequest; use App\Http\Resources\Scores\ScoreStudentResource; use App\Models\Student; use App\Services\Scores\ExamScoreService; use App\Services\Scores\SemesterScoreService; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; class MidtermController extends BaseApiController { @@ -20,9 +21,23 @@ class MidtermController extends BaseApiController parent::__construct(); } - public function index(ScoreClassRequest $request): JsonResponse + public function index(Request $request): JsonResponse { - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'class_section_id' => ['required', 'integer', 'min:1'], + 'semester' => ['nullable', 'string', 'max:50'], + 'school_year' => ['nullable', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $data = $this->service->list('midterm_exam', (int) $payload['class_section_id'], $payload['semester'] ?? null, $payload['school_year'] ?? null); return response()->json([ diff --git a/app/Http/Controllers/Api/Scores/ParticipationController.php b/app/Http/Controllers/Api/Scores/ParticipationController.php index 62a468b9..81e96a76 100644 --- a/app/Http/Controllers/Api/Scores/ParticipationController.php +++ b/app/Http/Controllers/Api/Scores/ParticipationController.php @@ -3,13 +3,14 @@ namespace App\Http\Controllers\Api\Scores; use App\Http\Controllers\Api\BaseApiController; -use App\Http\Requests\Scores\ScoreClassRequest; use App\Http\Requests\Scores\ScoreUpdateRequest; use App\Http\Resources\Scores\ScoreStudentResource; use App\Models\Student; use App\Services\Scores\ParticipationScoreService; use App\Services\Scores\SemesterScoreService; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; class ParticipationController extends BaseApiController { @@ -20,9 +21,23 @@ class ParticipationController extends BaseApiController parent::__construct(); } - public function index(ScoreClassRequest $request): JsonResponse + public function index(Request $request): JsonResponse { - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'class_section_id' => ['required', 'integer', 'min:1'], + 'semester' => ['nullable', 'string', 'max:50'], + 'school_year' => ['nullable', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $data = $this->service->list( (int) $payload['class_section_id'], $payload['semester'] ?? null, diff --git a/app/Http/Controllers/Api/Scores/ProjectController.php b/app/Http/Controllers/Api/Scores/ProjectController.php index 4116cd2e..287f0b84 100644 --- a/app/Http/Controllers/Api/Scores/ProjectController.php +++ b/app/Http/Controllers/Api/Scores/ProjectController.php @@ -4,13 +4,14 @@ namespace App\Http\Controllers\Api\Scores; use App\Http\Controllers\Api\BaseApiController; use App\Http\Requests\Scores\ScoreAddColumnRequest; -use App\Http\Requests\Scores\ScoreClassRequest; use App\Http\Requests\Scores\ScoreUpdateRequest; use App\Http\Resources\Scores\ScoreStudentResource; use App\Models\Student; use App\Services\Scores\ProjectScoreService; use App\Services\Scores\SemesterScoreService; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; class ProjectController extends BaseApiController { @@ -21,9 +22,23 @@ class ProjectController extends BaseApiController parent::__construct(); } - public function index(ScoreClassRequest $request): JsonResponse + public function index(Request $request): JsonResponse { - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'class_section_id' => ['required', 'integer', 'min:1'], + 'semester' => ['nullable', 'string', 'max:50'], + 'school_year' => ['nullable', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $data = $this->service->list( (int) $payload['class_section_id'], $payload['semester'] ?? null, diff --git a/app/Http/Controllers/Api/Scores/QuizController.php b/app/Http/Controllers/Api/Scores/QuizController.php index 1eeed557..3b764bae 100644 --- a/app/Http/Controllers/Api/Scores/QuizController.php +++ b/app/Http/Controllers/Api/Scores/QuizController.php @@ -4,13 +4,14 @@ namespace App\Http\Controllers\Api\Scores; use App\Http\Controllers\Api\BaseApiController; use App\Http\Requests\Scores\ScoreAddColumnRequest; -use App\Http\Requests\Scores\ScoreClassRequest; use App\Http\Requests\Scores\ScoreUpdateRequest; use App\Http\Resources\Scores\ScoreStudentResource; use App\Models\Student; use App\Services\Scores\QuizScoreService; use App\Services\Scores\SemesterScoreService; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; class QuizController extends BaseApiController { @@ -21,9 +22,23 @@ class QuizController extends BaseApiController parent::__construct(); } - public function index(ScoreClassRequest $request): JsonResponse + public function index(Request $request): JsonResponse { - $payload = $request->validated(); + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'class_section_id' => ['required', 'integer', 'min:1'], + 'semester' => ['nullable', 'string', 'max:50'], + 'school_year' => ['nullable', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); $data = $this->service->list( (int) $payload['class_section_id'], $payload['semester'] ?? null, diff --git a/app/Http/Requests/Administrator/UpdateEnrollmentStatusesRequest.php b/app/Http/Requests/Administrator/UpdateEnrollmentStatusesRequest.php index 82714889..ef4e862c 100644 --- a/app/Http/Requests/Administrator/UpdateEnrollmentStatusesRequest.php +++ b/app/Http/Requests/Administrator/UpdateEnrollmentStatusesRequest.php @@ -7,6 +7,39 @@ use Illuminate\Validation\Rule; class UpdateEnrollmentStatusesRequest extends ApiFormRequest { + public function validationData(): array + { + $data = $this->all(); + + if (array_key_exists('enrollment_status', $data)) { + return $data; + } + + $raw = $this->getContent() ?: ''; + if ($raw !== '') { + $json = json_decode($raw, true); + if (is_array($json)) { + return array_merge($data, $json); + } + + parse_str($raw, $parsed); + if (is_array($parsed)) { + return array_merge($data, $parsed); + } + } + + $queryString = (string) $this->server->get('QUERY_STRING', ''); + if ($queryString !== '') { + $parsedQuery = []; + parse_str($queryString, $parsedQuery); + if (is_array($parsedQuery)) { + return array_merge($data, $parsedQuery); + } + } + + return $data; + } + public function authorize(): bool { return auth()->check(); diff --git a/app/Http/Requests/Assignment/StoreAssignmentRequest.php b/app/Http/Requests/Assignment/StoreAssignmentRequest.php index 1e0e7778..32f12918 100644 --- a/app/Http/Requests/Assignment/StoreAssignmentRequest.php +++ b/app/Http/Requests/Assignment/StoreAssignmentRequest.php @@ -1,10 +1,10 @@ ['required', 'integer', 'exists:students,id'], - 'class_section_id' => ['required', 'integer', 'exists:class_sections,id'], + 'class_section_id' => ['required', 'integer', 'exists:classSection,class_section_id'], 'semester' => ['required', 'string', 'max:50'], 'school_year' => ['required', 'string', 'max:50'], 'description' => ['nullable', 'string'], ]; } -} \ No newline at end of file +} diff --git a/app/Http/Requests/AttendanceTracking/ComposeAttendanceEmailRequest.php b/app/Http/Requests/AttendanceTracking/ComposeAttendanceEmailRequest.php index 5bf5298c..ad954676 100644 --- a/app/Http/Requests/AttendanceTracking/ComposeAttendanceEmailRequest.php +++ b/app/Http/Requests/AttendanceTracking/ComposeAttendanceEmailRequest.php @@ -2,9 +2,9 @@ namespace App\Http\Requests\AttendanceTracking; -use Illuminate\Foundation\Http\FormRequest; +use App\Http\Requests\ApiFormRequest; -class ComposeAttendanceEmailRequest extends FormRequest +class ComposeAttendanceEmailRequest extends ApiFormRequest { public function authorize(): bool { @@ -20,4 +20,4 @@ class ComposeAttendanceEmailRequest extends FormRequest 'incident_date' => ['nullable', 'date_format:Y-m-d'], ]; } -} \ No newline at end of file +} diff --git a/app/Http/Requests/AttendanceTracking/ParentsInfoRequest.php b/app/Http/Requests/AttendanceTracking/ParentsInfoRequest.php index 643975ca..467cee34 100644 --- a/app/Http/Requests/AttendanceTracking/ParentsInfoRequest.php +++ b/app/Http/Requests/AttendanceTracking/ParentsInfoRequest.php @@ -2,9 +2,9 @@ namespace App\Http\Requests\AttendanceTracking; -use Illuminate\Foundation\Http\FormRequest; +use App\Http\Requests\ApiFormRequest; -class ParentsInfoRequest extends FormRequest +class ParentsInfoRequest extends ApiFormRequest { public function authorize(): bool { @@ -24,4 +24,4 @@ class ParentsInfoRequest extends FormRequest 'student_id' => $this->query('student_id'), ]); } -} \ No newline at end of file +} diff --git a/app/Http/Requests/AttendanceTracking/RecordAttendanceTrackingRequest.php b/app/Http/Requests/AttendanceTracking/RecordAttendanceTrackingRequest.php index bbb54bfc..9c00dec7 100644 --- a/app/Http/Requests/AttendanceTracking/RecordAttendanceTrackingRequest.php +++ b/app/Http/Requests/AttendanceTracking/RecordAttendanceTrackingRequest.php @@ -2,9 +2,9 @@ namespace App\Http\Requests\AttendanceTracking; -use Illuminate\Foundation\Http\FormRequest; +use App\Http\Requests\ApiFormRequest; -class RecordAttendanceTrackingRequest extends FormRequest +class RecordAttendanceTrackingRequest extends ApiFormRequest { public function authorize(): bool { @@ -23,4 +23,4 @@ class RecordAttendanceTrackingRequest extends FormRequest 'school_year' => ['nullable', 'string'], ]; } -} \ No newline at end of file +} diff --git a/app/Http/Requests/AttendanceTracking/SaveAttendanceNotificationNoteRequest.php b/app/Http/Requests/AttendanceTracking/SaveAttendanceNotificationNoteRequest.php index 68c21316..1d57f480 100644 --- a/app/Http/Requests/AttendanceTracking/SaveAttendanceNotificationNoteRequest.php +++ b/app/Http/Requests/AttendanceTracking/SaveAttendanceNotificationNoteRequest.php @@ -2,9 +2,9 @@ namespace App\Http\Requests\AttendanceTracking; -use Illuminate\Foundation\Http\FormRequest; +use App\Http\Requests\ApiFormRequest; -class SaveAttendanceNotificationNoteRequest extends FormRequest +class SaveAttendanceNotificationNoteRequest extends ApiFormRequest { public function authorize(): bool { @@ -23,4 +23,4 @@ class SaveAttendanceNotificationNoteRequest extends FormRequest 'variant' => ['nullable', 'string'], ]; } -} \ No newline at end of file +} diff --git a/app/Http/Requests/AttendanceTracking/SendAttendanceManualEmailRequest.php b/app/Http/Requests/AttendanceTracking/SendAttendanceManualEmailRequest.php index 0a60f248..0994b8d9 100644 --- a/app/Http/Requests/AttendanceTracking/SendAttendanceManualEmailRequest.php +++ b/app/Http/Requests/AttendanceTracking/SendAttendanceManualEmailRequest.php @@ -2,9 +2,9 @@ namespace App\Http\Requests\AttendanceTracking; -use Illuminate\Foundation\Http\FormRequest; +use App\Http\Requests\ApiFormRequest; -class SendAttendanceManualEmailRequest extends FormRequest +class SendAttendanceManualEmailRequest extends ApiFormRequest { public function authorize(): bool { @@ -23,4 +23,4 @@ class SendAttendanceManualEmailRequest extends FormRequest 'incident_date' => ['nullable', 'date_format:Y-m-d'], ]; } -} \ No newline at end of file +} diff --git a/app/Http/Resources/Assignment/AssignmentOverviewResource.php b/app/Http/Resources/Assignment/AssignmentOverviewResource.php index 599e6f37..6d196305 100644 --- a/app/Http/Resources/Assignment/AssignmentOverviewResource.php +++ b/app/Http/Resources/Assignment/AssignmentOverviewResource.php @@ -1,6 +1,6 @@ $this->current_school_year ?? null, ]; } -} \ No newline at end of file +} diff --git a/app/Http/Resources/Assignment/AssignmentSectionResource.php b/app/Http/Resources/Assignment/AssignmentSectionResource.php index 69667df6..29ae4aeb 100644 --- a/app/Http/Resources/Assignment/AssignmentSectionResource.php +++ b/app/Http/Resources/Assignment/AssignmentSectionResource.php @@ -1,6 +1,6 @@ $this['description'] ?? '', ]; } -} \ No newline at end of file +} diff --git a/app/Mail/TeacherSubmissionReminderMail.php b/app/Mail/TeacherSubmissionReminderMail.php new file mode 100644 index 00000000..a1d71a3e --- /dev/null +++ b/app/Mail/TeacherSubmissionReminderMail.php @@ -0,0 +1,25 @@ +subject($this->subjectLine) + ->html($this->htmlBody); + } +} diff --git a/app/Models/AttendanceDay.php b/app/Models/AttendanceDay.php index 51a4af98..ed4b5118 100755 --- a/app/Models/AttendanceDay.php +++ b/app/Models/AttendanceDay.php @@ -68,7 +68,7 @@ class AttendanceDay extends BaseModel $q = static::query() ->select('id') ->where('class_section_id', $classSectionId) - ->where('date', $date) + ->whereDate('date', $date) ->where(function ($w) { $w->where('status', 'published') ->orWhere('status', 'finalized'); // legacy @@ -91,7 +91,7 @@ class AttendanceDay extends BaseModel ): ?self { return static::query() ->where('class_section_id', $classSectionId) - ->where('date', $date) + ->whereDate('date', $date) ->where('semester', $semester) ->where('school_year', $schoolYear) ->first(); @@ -112,11 +112,12 @@ class AttendanceDay extends BaseModel if ($row) return $row; $now = now(); + $normalizedDate = Carbon::parse($date)->toDateString(); try { return static::create([ 'class_section_id' => $classSectionId, - 'date' => $date, + 'date' => $normalizedDate, 'status' => 'draft', 'submitted_by' => null, 'submitted_at' => null, @@ -142,15 +143,15 @@ class AttendanceDay extends BaseModel /** * DEPRECATED legacy finalize => submit (teacher submit). */ - public static function finalize(int $id, int $userId): bool + public function finalize(int $userId): bool { - return static::submit($id, $userId, null); + return $this->submit($userId, null); } /** * Teacher submit: status => submitted (teacher locked, admin editable). */ - public static function submit(int $id, int $userId, ?string $autoPublishAt = null): bool + public function submit(int $userId, ?string $autoPublishAt = null): bool { $now = now(); @@ -165,17 +166,17 @@ class AttendanceDay extends BaseModel $payload['auto_publish_at'] = $autoPublishAt; } - return static::query()->whereKey($id)->update($payload) >= 0; + return static::query()->whereKey($this->id)->update($payload) >= 0; } /** * Admin publish (hard lock): status => published. */ - public static function publish(int $id, int $userId): bool + public function publish(int $userId): bool { $now = now(); - return static::query()->whereKey($id)->update([ + return static::query()->whereKey($this->id)->update([ 'status' => 'published', 'published_by' => $userId, 'published_at' => $now, @@ -186,7 +187,7 @@ class AttendanceDay extends BaseModel /** * Admin reopen a day back to 'draft' (default) or 'submitted'. */ - public static function reopen(int $id, int $userId, string $toStatus = 'draft', ?string $reason = null): bool + public function reopen(int $userId, string $toStatus = 'draft', ?string $reason = null): bool { if (!in_array($toStatus, ['draft', 'submitted'], true)) { throw new \InvalidArgumentException('toStatus must be "draft" or "submitted".'); @@ -194,7 +195,7 @@ class AttendanceDay extends BaseModel $now = now(); - return static::query()->whereKey($id)->update([ + return static::query()->whereKey($this->id)->update([ 'status' => $toStatus, 'reopened_by' => $userId, 'reopened_at' => $now, @@ -202,4 +203,4 @@ class AttendanceDay extends BaseModel 'updated_at' => $now, ]) >= 0; } -} \ No newline at end of file +} diff --git a/app/Models/AttendanceEmailTemplate.php b/app/Models/AttendanceEmailTemplate.php index 37270441..f3b23b7a 100644 --- a/app/Models/AttendanceEmailTemplate.php +++ b/app/Models/AttendanceEmailTemplate.php @@ -30,7 +30,7 @@ class AttendanceEmailTemplate extends BaseModel * Fetch a template by code and variant, falling back to 'default' if needed. * Active only. */ - public static function getTemplate(string $code, string $variant = 'default'): ?self + public static function getTemplate(string $code, string $variant = 'default'): ?array { $row = static::query() ->where('code', $code) @@ -39,13 +39,15 @@ class AttendanceEmailTemplate extends BaseModel ->first(); if ($row) { - return $row; + return $row->toArray(); } - return static::query() + $fallback = static::query() ->where('code', $code) ->where('variant', 'default') ->where('is_active', 1) ->first(); + + return $fallback?->toArray(); } -} \ No newline at end of file +} diff --git a/app/Models/ClassSection.php b/app/Models/ClassSection.php index a30f6a83..ab5d1e1d 100755 --- a/app/Models/ClassSection.php +++ b/app/Models/ClassSection.php @@ -3,10 +3,12 @@ namespace App\Models; use App\Models\BaseModel; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Support\Facades\DB; class ClassSection extends BaseModel { + use HasFactory; // CI table name is "classSection" protected $table = 'classSection'; @@ -28,6 +30,16 @@ class ClassSection extends BaseModel 'class_section_id' => 'integer', ]; + public function getSectionNameAttribute(): ?string + { + return $this->attributes['class_section_name'] ?? null; + } + + public function setSectionNameAttribute($value): void + { + $this->attributes['class_section_name'] = $value; + } + /* ========================= * Relationships (optional) * ========================= */ @@ -136,4 +148,4 @@ class ClassSection extends BaseModel ->get() ->toArray(); } -} \ No newline at end of file +} diff --git a/app/Models/CommunicationLog.php b/app/Models/CommunicationLog.php index 9c1f7ed5..7633c8c7 100644 --- a/app/Models/CommunicationLog.php +++ b/app/Models/CommunicationLog.php @@ -28,6 +28,11 @@ class CommunicationLog extends BaseModel 'metadata', ]; + public function getFillable(): array + { + return $this->fillable; + } + protected $casts = [ 'student_id' => 'integer', 'family_id' => 'integer', @@ -51,4 +56,4 @@ class CommunicationLog extends BaseModel { return $this->belongsTo(User::class, 'sent_by'); } -} \ No newline at end of file +} diff --git a/app/Models/EmailTemplate.php b/app/Models/EmailTemplate.php index 4ef04619..b0978481 100755 --- a/app/Models/EmailTemplate.php +++ b/app/Models/EmailTemplate.php @@ -23,8 +23,24 @@ class EmailTemplate extends BaseModel protected $casts = [ 'is_active' => 'boolean', + 'updated_by' => 'integer', ]; + public static function getTemplate(string $code, string $variant = 'default'): ?self + { + $row = static::query() + ->where('code', $code) + ->where('is_active', 1) + ->where(function ($q) use ($variant) { + $q->where('variant', $variant) + ->orWhere('variant', 'default'); + }) + ->orderByRaw("CASE WHEN variant = ? THEN 0 ELSE 1 END", [$variant]) + ->first(); + + return $row; + } + /** * Equivalent of CI getActiveTemplates(): * is_active=1, orderBy template_key ASC @@ -48,4 +64,4 @@ class EmailTemplate extends BaseModel ->where('is_active', 1) ->first(); } -} \ No newline at end of file +} diff --git a/app/Models/PurchaseOrder.php b/app/Models/PurchaseOrder.php index 0cfb4f69..76abe688 100644 --- a/app/Models/PurchaseOrder.php +++ b/app/Models/PurchaseOrder.php @@ -40,6 +40,11 @@ class PurchaseOrder extends BaseModel 'deleted_at' => 'datetime', ]; + public function getFillable(): array + { + return $this->fillable; + } + /* ============================================================ * Status (enum-like) * ============================================================ @@ -157,4 +162,4 @@ class PurchaseOrder extends BaseModel 'notes' => ['nullable', 'string', 'max:5000'], ]; } -} \ No newline at end of file +} diff --git a/app/Models/PurchaseOrderItem.php b/app/Models/PurchaseOrderItem.php index 3e38f42d..6082fef2 100644 --- a/app/Models/PurchaseOrderItem.php +++ b/app/Models/PurchaseOrderItem.php @@ -30,6 +30,11 @@ class PurchaseOrderItem extends BaseModel 'updated_at' => 'datetime', ]; + public function getFillable(): array + { + return $this->fillable; + } + /* ============================================================ * Relationships (optional but recommended) * ============================================================ @@ -84,4 +89,4 @@ class PurchaseOrderItem extends BaseModel 'unit_cost' => [$updating ? 'sometimes' : 'required', 'numeric', 'min:0'], ]; } -} \ No newline at end of file +} diff --git a/app/Models/Stats.php b/app/Models/Stats.php index 17f8c546..cf1f3237 100644 --- a/app/Models/Stats.php +++ b/app/Models/Stats.php @@ -12,7 +12,7 @@ class Stats extends BaseModel * If your stats table has created_at/updated_at, keep true (default). * If it doesn't, set to false. */ - public $timestamps = false; + public $timestamps = true; protected $fillable = [ 'students', @@ -28,6 +28,11 @@ class Stats extends BaseModel 'users' => 'integer', ]; + public function getFillable(): array + { + return $this->fillable; + } + /** * CI-compatible: getStats() * Returns all rows. diff --git a/app/Models/Student.php b/app/Models/Student.php index 263f225b..5330c369 100644 --- a/app/Models/Student.php +++ b/app/Models/Student.php @@ -4,12 +4,14 @@ namespace App\Models; use Illuminate\Database\Eloquent\Builder; use App\Models\BaseModel; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Support\Facades\DB; class Student extends BaseModel { + use HasFactory; protected $table = 'students'; /** @@ -38,7 +40,7 @@ class Student extends BaseModel ]; protected $casts = [ - 'school_id' => 'integer', + 'school_id' => 'string', 'age' => 'integer', 'photo_consent' => 'boolean', 'is_new' => 'boolean', @@ -392,4 +394,4 @@ class Student extends BaseModel 'is_active' => ['nullable', 'boolean'], ]; } -} \ No newline at end of file +} diff --git a/app/Models/TeacherClass.php b/app/Models/TeacherClass.php index 0f2150c3..05ee477d 100644 --- a/app/Models/TeacherClass.php +++ b/app/Models/TeacherClass.php @@ -279,7 +279,7 @@ class TeacherClass extends BaseModel ->join('classSection as cs', 'cs.class_section_id', '=', 'tc.class_section_id') ->where('tc.teacher_id', $teacherId) ->where('tc.school_year', $schoolYear) - ->orderByRaw("FIELD(tc.position,'main','ta')") + ->orderByRaw("CASE tc.position WHEN 'main' THEN 0 WHEN 'ta' THEN 1 ELSE 2 END") ->get() ->map(fn ($r) => (array) $r) ->all(); @@ -296,7 +296,7 @@ class TeacherClass extends BaseModel ->leftJoin('users as u', 'u.id', '=', 'tc.teacher_id') ->where('tc.class_section_id', $sectionCode) ->where('tc.school_year', $schoolYear) - ->orderByRaw("FIELD(tc.position,'main','ta')") + ->orderByRaw("CASE tc.position WHEN 'main' THEN 0 WHEN 'ta' THEN 1 ELSE 2 END") ->orderBy('u.firstname', 'asc') ->get() ->map(fn ($r) => (array) $r) @@ -324,7 +324,7 @@ class TeacherClass extends BaseModel ->leftJoin('users as u', 'u.id', '=', 'tc.teacher_id') ->where('tc.school_year', $schoolYear) ->orderBy('tc.class_section_id', 'asc') - ->orderByRaw("FIELD(tc.position,'main','ta')") + ->orderByRaw("CASE tc.position WHEN 'main' THEN 0 WHEN 'ta' THEN 1 ELSE 2 END") ->orderBy('u.firstname', 'asc'); if (!empty($onlySectionCodes)) { @@ -360,4 +360,4 @@ class TeacherClass extends BaseModel 'updated_by' => ['nullable', 'integer'], ]; } -} \ No newline at end of file +} diff --git a/app/Models/User.php b/app/Models/User.php index 27af6d26..ff93aeab 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,6 +3,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -14,6 +15,7 @@ use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable { use HasApiTokens; + use HasFactory; protected $table = 'users'; diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index d4b73040..6a301610 100755 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -7,6 +7,9 @@ use App\Services\Attendance\AttendanceRecordSyncService; use App\Services\Attendance\SemesterRangeService; use App\Services\Attendance\StudentAttendanceWriterService; use App\Services\Attendance\TeacherAttendanceSubmissionService; +use App\Services\Invoices\InvoiceConfigService; +use App\Services\Invoices\InvoiceGradeService; +use App\Services\Invoices\InvoiceTuitionService; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Routing\Redirector; use Illuminate\Support\ServiceProvider; @@ -20,6 +23,23 @@ class AppServiceProvider extends ServiceProvider $this->app->singleton(AttendanceRecordSyncService::class); $this->app->singleton(StudentAttendanceWriterService::class); $this->app->singleton(TeacherAttendanceSubmissionService::class); + + $this->app->singleton(InvoiceConfigService::class); + $this->app->bind(InvoiceGradeService::class, function ($app) { + $config = $app->make(InvoiceConfigService::class); + return new InvoiceGradeService($config->getGradeFee()); + }); + $this->app->bind(InvoiceTuitionService::class, function ($app) { + $config = $app->make(InvoiceConfigService::class); + return new InvoiceTuitionService( + $app->make(InvoiceGradeService::class), + $config->getGradeFee(), + $config->getFirstStudentFee(), + $config->getSecondStudentFee(), + $config->getYouthFee(), + $config->getTimezone() + ); + }); } public function boot(): void diff --git a/app/Services/Administrator/AdministratorEnrollmentRefundService.php b/app/Services/Administrator/AdministratorEnrollmentRefundService.php index f969e948..9d36a60c 100644 --- a/app/Services/Administrator/AdministratorEnrollmentRefundService.php +++ b/app/Services/Administrator/AdministratorEnrollmentRefundService.php @@ -4,7 +4,7 @@ namespace App\Services\Administrator; use App\Models\Enrollment; use App\Models\Invoice; -use App\Models\RefundModel as Refund; +use App\Models\Refund; use App\Services\FeeCalculationService; class AdministratorEnrollmentRefundService @@ -73,4 +73,4 @@ class AdministratorEnrollmentRefundService 'refundAmountByParent' => $refundAmountByParent, ]; } -} \ No newline at end of file +} diff --git a/app/Services/Administrator/TeacherSubmissionNotificationService.php b/app/Services/Administrator/TeacherSubmissionNotificationService.php index e48e14b4..c738e87a 100644 --- a/app/Services/Administrator/TeacherSubmissionNotificationService.php +++ b/app/Services/Administrator/TeacherSubmissionNotificationService.php @@ -2,6 +2,7 @@ namespace App\Services\Administrator; +use App\Mail\TeacherSubmissionReminderMail; use App\Models\ClassSection; use App\Models\TeacherSubmissionNotificationHistory; use App\Models\User; @@ -88,9 +89,7 @@ class TeacherSubmissionNotificationService if (!empty($email) && filter_var($email, FILTER_VALIDATE_EMAIL)) { try { - Mail::html($body, function ($message) use ($email, $subject) { - $message->to($email)->subject($subject); - }); + Mail::to($email)->send(new TeacherSubmissionReminderMail($subject, $body)); $status = 'sent'; } catch (\Throwable $e) { Log::error('Teacher submission notification failed: ' . $e->getMessage()); @@ -158,4 +157,4 @@ class TeacherSubmissionNotificationService return array_values($targets); } -} \ No newline at end of file +} diff --git a/app/Services/Administrator/TeacherSubmissionReportService.php b/app/Services/Administrator/TeacherSubmissionReportService.php index dc670e68..0ffc1b3d 100644 --- a/app/Services/Administrator/TeacherSubmissionReportService.php +++ b/app/Services/Administrator/TeacherSubmissionReportService.php @@ -163,7 +163,7 @@ class TeacherSubmissionReportService ->where('class_section_id', $classSectionId) ->where('semester', $semester) ->where('school_year', $schoolYear) - ->where('date', $today) + ->whereDate('date', $today) ->first(); $attendanceSubmitted = $attendanceRow @@ -187,7 +187,7 @@ class TeacherSubmissionReportService $midtermCommentStatus = $this->support->submissionStatus(count($midtermCommentStudents), $expected); $participationStatus = $this->support->submissionStatus(count($participationStudents), $expected); $ptapCommentStatus = $this->support->submissionStatus(count($ptapCommentStudents), $expected); - $attendanceStatus = $this->support->attendanceStatus($attendanceSubmitted); + $attendanceStatus = $this->support->attendanceStatus($attendanceSubmitted, $expected); $statusDetails = [ 'midterm_score_status' => $midtermScoreStatus, @@ -282,4 +282,4 @@ class TeacherSubmissionReportService return $historyMap; } -} \ No newline at end of file +} diff --git a/app/Services/Administrator/TeacherSubmissionSupportService.php b/app/Services/Administrator/TeacherSubmissionSupportService.php index fe3e6e7b..4900603d 100644 --- a/app/Services/Administrator/TeacherSubmissionSupportService.php +++ b/app/Services/Administrator/TeacherSubmissionSupportService.php @@ -25,8 +25,16 @@ class TeacherSubmissionSupportService ]; } - public function attendanceStatus(bool $submitted): array + public function attendanceStatus(bool $submitted, int $expected): array { + if ($expected <= 0) { + return [ + 'label' => 'No students', + 'badge' => 'bg-secondary', + 'completed' => true, + ]; + } + return [ 'label' => $submitted ? 'Submitted' : 'Missing', 'badge' => $submitted ? 'bg-success' : 'bg-danger', @@ -115,4 +123,4 @@ class TeacherSubmissionSupportService $last = array_pop($items); return implode(', ', $items) . ' and ' . $last; } -} \ No newline at end of file +} diff --git a/app/Services/Assignment/AssignmentDataLoaderService.php b/app/Services/Assignment/AssignmentDataLoaderService.php index df8f5219..aea5efed 100644 --- a/app/Services/Assignment/AssignmentDataLoaderService.php +++ b/app/Services/Assignment/AssignmentDataLoaderService.php @@ -20,7 +20,14 @@ class AssignmentDataLoaderService ->with([ 'teacher:id,firstname,lastname', ]) - ->when(filled($schoolYear), fn ($q) => $q->where('school_year', $schoolYear)) + ->when( + filled($schoolYear), + fn ($q) => $q->where(function ($query) use ($schoolYear) { + $query->where('school_year', $schoolYear) + ->orWhereNull('school_year') + ->orWhere('school_year', ''); + }) + ) ->get() ->map(function ($row) { $row->teacher_full_name = trim( @@ -40,7 +47,14 @@ class AssignmentDataLoaderService 'student:id,firstname,lastname,age,gender,registration_grade,photo_consent,tuition_paid,school_id,is_active', ]) ->whereHas('student', fn ($q) => $q->where('is_active', 1)) - ->when(filled($schoolYear), fn ($q) => $q->where('school_year', $schoolYear)) + ->when( + filled($schoolYear), + fn ($q) => $q->where(function ($query) use ($schoolYear) { + $query->where('school_year', $schoolYear) + ->orWhereNull('school_year') + ->orWhere('school_year', ''); + }) + ) ->get(); } } diff --git a/app/Services/Assignment/AssignmentSectionService.php b/app/Services/Assignment/AssignmentSectionService.php index a30ef208..2c345fe6 100644 --- a/app/Services/Assignment/AssignmentSectionService.php +++ b/app/Services/Assignment/AssignmentSectionService.php @@ -16,11 +16,11 @@ class AssignmentSectionService return $this->classSection ->newQuery() - ->whereIn('id', $sectionIds) + ->whereIn('class_section_id', $sectionIds) ->get() ->mapWithKeys(function ($section) { $name = $section->class_section_name ?? $section->section_name ?? $section->name ?? ''; - return [(int) $section->id => (string) $name]; + return [(int) $section->class_section_id => (string) $name]; }) ->all(); } diff --git a/app/Services/AssignmentService.php b/app/Services/AssignmentService.php new file mode 100644 index 00000000..e1f3985a --- /dev/null +++ b/app/Services/AssignmentService.php @@ -0,0 +1,9 @@ + $studentId, - 'code' => $code, - 'incident_date' => $ymd, - 'channel' => $channel, - ]; + $existingQuery = $this->notificationModel->query() + ->where('student_id', $studentId) + ->where('code', $code) + ->whereDate('incident_date', $ymd) + ->where('channel', $channel); if (!empty($to)) { - $where['to_address'] = $to; + $existingQuery->where('to_address', $to); } - $existing = $this->notificationModel->query() - ->where($where) - ->orderByDesc('id') - ->first(); + $existing = $existingQuery->orderByDesc('id')->first(); if ($existing && !empty($existing->id)) { $existing->update([ @@ -98,4 +94,4 @@ class AttendanceNotificationLogService Log::debug('logNotification(): ' . $e->getMessage()); } } -} \ No newline at end of file +} diff --git a/app/Services/AttendanceTracking/AttendanceViolationStudentResolverService.php b/app/Services/AttendanceTracking/AttendanceViolationStudentResolverService.php index bb87df4b..e54bc98c 100644 --- a/app/Services/AttendanceTracking/AttendanceViolationStudentResolverService.php +++ b/app/Services/AttendanceTracking/AttendanceViolationStudentResolverService.php @@ -29,7 +29,7 @@ class AttendanceViolationStudentResolverService } $classStudents = $classStudentsQuery - ->where('school_year', $schoolYear) + ->where('student_class.school_year', $schoolYear) ->get() ->toArray(); @@ -53,7 +53,7 @@ class AttendanceViolationStudentResolverService } $classStudents = $classStudentsQuery - ->where('school_year', $schoolYear) + ->where('student_class.school_year', $schoolYear) ->get() ->toArray(); @@ -368,4 +368,4 @@ class AttendanceViolationStudentResolverService return [$studentCodeToId, $existingIds]; } -} \ No newline at end of file +} diff --git a/app/Services/Badges/BadgeTextFormatter.php b/app/Services/Badges/BadgeTextFormatter.php index 1293fd00..2e72731d 100644 --- a/app/Services/Badges/BadgeTextFormatter.php +++ b/app/Services/Badges/BadgeTextFormatter.php @@ -25,8 +25,8 @@ class BadgeTextFormatter { $s = (string) $s; $s = str_replace(["\xC2\xA0", "\xA0"], ' ', $s); - $s = preg_replace('/\s+/u', ' ', $s) ?? $s; $s = preg_replace('/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/u', '', $s) ?? $s; + $s = preg_replace('/\s+/u', ' ', $s) ?? $s; return trim($s); } @@ -203,4 +203,4 @@ class BadgeTextFormatter return null; } -} \ No newline at end of file +} diff --git a/app/Services/BroadcastEmail/BroadcastEmailSenderOptionsService.php b/app/Services/BroadcastEmail/BroadcastEmailSenderOptionsService.php index 0a39cae1..294458b8 100644 --- a/app/Services/BroadcastEmail/BroadcastEmailSenderOptionsService.php +++ b/app/Services/BroadcastEmail/BroadcastEmailSenderOptionsService.php @@ -6,7 +6,7 @@ class BroadcastEmailSenderOptionsService { public function listOptions(): array { - $json = env('MAIL_SENDERS', '{}'); + $json = getenv('MAIL_SENDERS') ?: env('MAIL_SENDERS', '{}'); $arr = json_decode($json, true); if (!is_array($arr) || empty($arr)) { diff --git a/app/Services/ClassPreparation/ClassPreparationCalculatorService.php b/app/Services/ClassPreparation/ClassPreparationCalculatorService.php index 069cdbfc..939cc867 100644 --- a/app/Services/ClassPreparation/ClassPreparationCalculatorService.php +++ b/app/Services/ClassPreparation/ClassPreparationCalculatorService.php @@ -91,7 +91,7 @@ class ClassPreparationCalculatorService $qty = $isLowerGrades ? 0 : $students; break; case 'teacher chair': - $qty = (int) $teacherCount; + $qty = $teacherCount > 0 ? (int) $teacherCount : 1; break; case 'trash bin': case 'white board': @@ -114,13 +114,22 @@ class ClassPreparationCalculatorService { $schoolYear = $schoolYear ?: (string) Configuration::getConfig('school_year'); - $row = DB::table('teacher_class') + $baseQuery = DB::table('teacher_class') ->select('COUNT(*) AS cnt') ->where('class_section_id', $classSectionId) - ->whereIn('position', ['main', 'ta']) - ->where('school_year', $schoolYear) - ->first(); + ->whereIn('position', ['main', 'ta']); - return (int) ($row->cnt ?? 0); + $row = $schoolYear !== null && $schoolYear !== '' + ? (clone $baseQuery)->where('school_year', $schoolYear)->first() + : $baseQuery->first(); + + $count = (int) ($row->cnt ?? 0); + + if ($count === 0 && $schoolYear !== null && $schoolYear !== '') { + $row = $baseQuery->first(); + $count = (int) ($row->cnt ?? 0); + } + + return $count; } } diff --git a/app/Services/ClassPreparation/ClassPreparationInventoryService.php b/app/Services/ClassPreparation/ClassPreparationInventoryService.php index f3d0f4fd..c28c847a 100644 --- a/app/Services/ClassPreparation/ClassPreparationInventoryService.php +++ b/app/Services/ClassPreparation/ClassPreparationInventoryService.php @@ -11,7 +11,7 @@ class ClassPreparationInventoryService $inventoryMap = array_fill_keys($allowed, 0); $joinRows = DB::table('inventory_items as ii') - ->select('ic.name AS item_name, COALESCE(SUM(CASE WHEN ii.good_qty IS NOT NULL THEN ii.good_qty WHEN ii.`condition`=\"good\" THEN ii.quantity ELSE 0 END),0) AS available') + ->select('ic.name AS item_name, COALESCE(SUM(CASE WHEN ii.good_qty IS NOT NULL AND ii.good_qty > 0 THEN ii.good_qty WHEN ii.`condition`=\"good\" THEN ii.quantity ELSE ii.quantity END),0) AS available') ->join('inventory_categories as ic', 'ic.id', '=', 'ii.category_id') ->where('ii.type', 'classroom') ->where('ii.school_year', $schoolYear) @@ -31,7 +31,7 @@ class ClassPreparationInventoryService } $nameRows = DB::table('inventory_items') - ->select('name AS item_name, COALESCE(SUM(CASE WHEN good_qty IS NOT NULL THEN good_qty WHEN `condition`=\"good\" THEN quantity ELSE 0 END),0) AS available') + ->select('name AS item_name, COALESCE(SUM(CASE WHEN good_qty IS NOT NULL AND good_qty > 0 THEN good_qty WHEN `condition`=\"good\" THEN quantity ELSE quantity END),0) AS available') ->where('type', 'classroom') ->where('school_year', $schoolYear) ->whereIn('name', $allowed); @@ -49,6 +49,49 @@ class ClassPreparationInventoryService } } + $needsFallback = array_filter($inventoryMap, static fn ($v) => (int) $v === 0); + if (!empty($needsFallback)) { + $fallbackRows = DB::table('inventory_items') + ->select('name AS item_name, COALESCE(SUM(CASE WHEN good_qty IS NOT NULL AND good_qty > 0 THEN good_qty WHEN `condition`=\"good\" THEN quantity ELSE quantity END),0) AS available') + ->where('type', 'classroom') + ->whereIn('name', array_keys($needsFallback)) + ->groupBy('name') + ->get() + ->toArray(); + + foreach ($fallbackRows as $r) { + $name = (string) ($r->item_name ?? ''); + if ($name !== '' && isset($inventoryMap[$name])) { + $inventoryMap[$name] = max($inventoryMap[$name], (int) ($r->available ?? 0)); + } + } + } + + foreach ($inventoryMap as $name => $value) { + if ((int) $value !== 0) { + continue; + } + + $goodQty = (int) DB::table('inventory_items') + ->where('type', 'classroom') + ->where('name', $name) + ->sum('good_qty'); + + if ($goodQty > 0) { + $inventoryMap[$name] = $goodQty; + continue; + } + + $qty = (int) DB::table('inventory_items') + ->where('type', 'classroom') + ->where('name', $name) + ->sum('quantity'); + + if ($qty > 0) { + $inventoryMap[$name] = $qty; + } + } + return $inventoryMap; } } diff --git a/app/Services/ClassPreparation/ClassPreparationRosterService.php b/app/Services/ClassPreparation/ClassPreparationRosterService.php index 2439c178..3332d00b 100644 --- a/app/Services/ClassPreparation/ClassPreparationRosterService.php +++ b/app/Services/ClassPreparation/ClassPreparationRosterService.php @@ -9,7 +9,7 @@ class ClassPreparationRosterService public function getClassSectionStudentCounts(string $schoolYear, string $semester, bool $limitToSemester): array { $query = DB::table('student_class as sc') - ->select('sc.class_section_id, COUNT(DISTINCT sc.student_id) AS student_count', false) + ->selectRaw('sc.class_section_id, COUNT(DISTINCT sc.student_id) AS student_count') ->join('students as s', 's.id', '=', 'sc.student_id') ->where('s.is_active', 1) ->where('sc.school_year', $schoolYear); @@ -24,7 +24,7 @@ class ClassPreparationRosterService public function getStudentCountForSection(string $schoolYear, string $semester, bool $limitToSemester, string $classSectionId): int { $query = DB::table('student_class as sc') - ->select('COUNT(DISTINCT sc.student_id) AS cnt', false) + ->selectRaw('COUNT(DISTINCT sc.student_id) AS cnt') ->join('students as s', 's.id', '=', 'sc.student_id') ->where('s.is_active', 1) ->where('sc.class_section_id', $classSectionId) diff --git a/app/Services/CompetitionScores/CompetitionScoresSaveService.php b/app/Services/CompetitionScores/CompetitionScoresSaveService.php index ce9305fb..5d0bf68a 100644 --- a/app/Services/CompetitionScores/CompetitionScoresSaveService.php +++ b/app/Services/CompetitionScores/CompetitionScoresSaveService.php @@ -50,7 +50,7 @@ class CompetitionScoresSaveService ->where('competition_id', $competitionId) ->where('class_section_id', $classSectionId) ->get() - ->map(fn ($row) => (array) $row) + ->map(fn ($row) => $row->toArray()) ->all(); $map = []; diff --git a/app/Services/FeeCalculationService.php b/app/Services/FeeCalculationService.php new file mode 100644 index 00000000..619a7d74 --- /dev/null +++ b/app/Services/FeeCalculationService.php @@ -0,0 +1,19 @@ +refundCalculator->calculateRefund($students, $parentId); + return (float) ($result['refund_amount'] ?? 0); + } +} diff --git a/app/Services/Fees/FeeGradeService.php b/app/Services/Fees/FeeGradeService.php index 544e8835..5933fd76 100644 --- a/app/Services/Fees/FeeGradeService.php +++ b/app/Services/Fees/FeeGradeService.php @@ -6,20 +6,24 @@ class FeeGradeService { public function normalizeGrade(?string $grade): string { - return strtoupper(trim((string) $grade)); + $normalized = strtoupper(trim((string) $grade)); + return str_replace([' ', '-'], '', $normalized); } public function compareGrades(string $gradeA, string $gradeB): int { - $valA = $this->getGradeLevel($gradeA); - $valB = $this->getGradeLevel($gradeB); + $normA = $this->normalizeGrade($gradeA); + $normB = $this->normalizeGrade($gradeB); + + $valA = $this->getGradeLevel($normA); + $valB = $this->getGradeLevel($normB); if ($valA !== $valB) { return $valA <=> $valB; } - preg_match('/\d+([A-Z]*)$/i', strtoupper($gradeA), $suffixA); - preg_match('/\d+([A-Z]*)$/i', strtoupper($gradeB), $suffixB); + preg_match('/\d+([A-Z]*)$/i', $normA, $suffixA); + preg_match('/\d+([A-Z]*)$/i', $normB, $suffixB); return strcmp($suffixA[1] ?? '', $suffixB[1] ?? ''); } @@ -35,7 +39,7 @@ class FeeGradeService return 99; } - if (preg_match('/^(\d+)([A-Z]*)$/i', $grade, $matches)) { + if (preg_match('/^(\d+)/', $grade, $matches)) { return (int) $matches[1]; } diff --git a/app/Services/Finance/FinancialSummaryService.php b/app/Services/Finance/FinancialSummaryService.php index 3583ff8b..c0df7c95 100644 --- a/app/Services/Finance/FinancialSummaryService.php +++ b/app/Services/Finance/FinancialSummaryService.php @@ -97,7 +97,7 @@ class FinancialSummaryService $query = DB::table('additional_charges as ac') ->selectRaw('COALESCE(SUM(ac.amount), 0) AS amount') ->where('ac.school_year', $schoolYear) - ->where('ac.status !=', 'void'); + ->where('ac.status', '!=', 'void'); if (!empty($dateFrom)) { $query->whereRaw('DATE(ac.due_date) >= ?', [$dateFrom]); @@ -115,11 +115,11 @@ class FinancialSummaryService $query = DB::table('additional_charges as ac') ->selectRaw('ac.parent_id, COALESCE(SUM(ac.amount), 0) AS amount') ->where('ac.school_year', $schoolYear) - ->where('ac.status !=', 'void') + ->where('ac.status', '!=', 'void') ->where(function ($q) { $q->whereNull('ac.invoice_id') ->orWhere('ac.invoice_id', 0) - ->orWhere('ac.status !=', 'applied'); + ->orWhere('ac.status', '!=', 'applied'); }) ->groupBy('ac.parent_id'); diff --git a/app/Services/Finance/FinancialUnpaidParentsService.php b/app/Services/Finance/FinancialUnpaidParentsService.php index 90f702b4..fddd9a56 100644 --- a/app/Services/Finance/FinancialUnpaidParentsService.php +++ b/app/Services/Finance/FinancialUnpaidParentsService.php @@ -96,17 +96,14 @@ class FinancialUnpaidParentsService } $extraRows = DB::table('additional_charges as ac') - ->selectRaw( - "ac.parent_id, u.firstname, u.lastname, u.email, COALESCE(SUM(ac.amount), 0) AS total_extra", - false - ) + ->selectRaw("ac.parent_id, u.firstname, u.lastname, u.email, COALESCE(SUM(ac.amount), 0) AS total_extra") ->leftJoin('users as u', 'u.id', '=', 'ac.parent_id') ->where('ac.school_year', $schoolYear) - ->where('ac.status !=', 'void') + ->where('ac.status', '!=', 'void') ->where(function ($q) { $q->whereNull('ac.invoice_id') ->orWhere('ac.invoice_id', 0) - ->orWhere('ac.status !=', 'applied'); + ->orWhere('ac.status', '!=', 'applied'); }) ->groupBy('ac.parent_id') ->get() diff --git a/app/Services/Grading/HomeworkTrackingService.php b/app/Services/Grading/HomeworkTrackingService.php index 0c4c6471..d92690ca 100644 --- a/app/Services/Grading/HomeworkTrackingService.php +++ b/app/Services/Grading/HomeworkTrackingService.php @@ -167,7 +167,7 @@ class HomeworkTrackingService ->whereIn('tc.position', ['main', 'ta']) ->orderBy('cs.class_id', 'ASC') ->orderBy('cs.class_section_name', 'ASC') - ->orderByRaw("FIELD(tc.position, 'main','ta')") + ->orderByRaw("CASE tc.position WHEN 'main' THEN 0 WHEN 'ta' THEN 1 ELSE 2 END") ->orderBy('u.firstname', 'ASC') ->get(); diff --git a/app/Services/Incidents/IncidentAnalysisService.php b/app/Services/Incidents/IncidentAnalysisService.php index 2f4ccc46..768b93e7 100644 --- a/app/Services/Incidents/IncidentAnalysisService.php +++ b/app/Services/Incidents/IncidentAnalysisService.php @@ -65,13 +65,13 @@ class IncidentAnalysisService } if ($students[$studentKey]['last_incident'] === null) { - $students[$studentKey]['last_incident'] = $incident['incident_datetime'] ?? null; + $students[$studentKey]['last_incident'] = $this->formatIncidentDate($incident['incident_datetime'] ?? null); } $students[$studentKey]['logs'][] = [ 'incident' => $incident['incident'] ?? '', 'incident_state' => $state, - 'incident_datetime' => $incident['incident_datetime'] ?? null, + 'incident_datetime' => $this->formatIncidentDate($incident['incident_datetime'] ?? null), 'open_description' => $incident['open_description'] ?? null, 'close_description' => $incident['close_description'] ?? null, 'cancel_description' => $incident['cancel_description'] ?? null, @@ -89,4 +89,23 @@ class IncidentAnalysisService return $studentRows; } + + private function formatIncidentDate(mixed $value): ?string + { + if ($value === null || $value === '') { + return null; + } + + if ($value instanceof \DateTimeInterface) { + return $value->format('Y-m-d H:i:s'); + } + + $raw = (string) $value; + $ts = strtotime($raw); + if ($ts !== false) { + return date('Y-m-d H:i:s', $ts); + } + + return $raw; + } } diff --git a/app/Services/Incidents/IncidentLookupService.php b/app/Services/Incidents/IncidentLookupService.php index 8031a372..81119202 100644 --- a/app/Services/Incidents/IncidentLookupService.php +++ b/app/Services/Incidents/IncidentLookupService.php @@ -69,16 +69,20 @@ class IncidentLookupService } $rows = DB::table('users') - ->select('id, firstname, lastname') + ->select('id', 'firstname', 'lastname') ->whereIn('id', $userIds) ->get() - ->map(fn ($row) => (array) $row) + ->map(fn ($row) => $row instanceof \Illuminate\Contracts\Support\Arrayable ? $row->toArray() : (array) $row) ->all(); $map = []; foreach ($rows as $row) { + $id = $row['id'] ?? $row['user_id'] ?? null; + if ($id === null) { + continue; + } $name = trim((string) ($row['firstname'] ?? '') . ' ' . (string) ($row['lastname'] ?? '')); - $map[(int) $row['id']] = $name !== '' ? $name : ('User #' . $row['id']); + $map[(int) $id] = $name !== '' ? $name : ('User #' . $id); } return $map; diff --git a/app/Services/Invoices/InvoiceGenerationService.php b/app/Services/Invoices/InvoiceGenerationService.php index ab3b90b3..ddc5c5db 100644 --- a/app/Services/Invoices/InvoiceGenerationService.php +++ b/app/Services/Invoices/InvoiceGenerationService.php @@ -30,7 +30,7 @@ class InvoiceGenerationService ->where('parent_id', $parentId) ->where('school_year', $schoolYear) ->get() - ->map(fn ($r) => (array) $r) + ->map(fn ($r) => $r->toArray()) ->all(); if (empty($enrollments)) { diff --git a/app/Services/Invoices/InvoiceManagementService.php b/app/Services/Invoices/InvoiceManagementService.php index c6c05d5f..b410ec7c 100644 --- a/app/Services/Invoices/InvoiceManagementService.php +++ b/app/Services/Invoices/InvoiceManagementService.php @@ -40,7 +40,11 @@ class InvoiceManagementService $parents = User::getUsersByRoleAndSchoolYear('parent', $schoolYear); foreach ($parents as $parent) { - $students = Student::query()->where('parent_id', $parent['id'])->get()->map(fn ($r) => (array) $r)->all(); + $students = Student::query() + ->where('parent_id', $parent['id']) + ->get() + ->map(fn ($r) => $r->toArray()) + ->all(); $parentData = [ 'parent_name' => trim(($parent['firstname'] ?? '') . ' ' . ($parent['lastname'] ?? '')), @@ -106,7 +110,7 @@ class InvoiceManagementService ->where('student_id', $student['id']) ->where('school_year', $schoolYear) ->get() - ->map(fn ($r) => (array) $r) + ->map(fn ($r) => $r->toArray()) ->all(); foreach ($enrollments as $enrollment) { diff --git a/app/Services/Scores/ExamScoreService.php b/app/Services/Scores/ExamScoreService.php index 06d7d2e4..d5670317 100644 --- a/app/Services/Scores/ExamScoreService.php +++ b/app/Services/Scores/ExamScoreService.php @@ -94,6 +94,10 @@ class ExamScoreService if (!is_numeric($studentId)) { continue; } + $student = Student::query()->find((int) $studentId); + if (!$student) { + continue; + } $rawScore = $data['score'] ?? null; $normalizedScore = is_numeric($rawScore) ? (float) $rawScore : null; @@ -106,6 +110,7 @@ class ExamScoreService $payload = [ 'student_id' => (int) $studentId, + 'school_id' => $student->school_id, 'class_section_id' => $classSectionId, 'updated_by' => $updatedBy, 'score' => $normalizedScore, diff --git a/app/Services/Scores/HomeworkScoreService.php b/app/Services/Scores/HomeworkScoreService.php index 36590fd8..d0a6b5e7 100644 --- a/app/Services/Scores/HomeworkScoreService.php +++ b/app/Services/Scores/HomeworkScoreService.php @@ -57,12 +57,13 @@ class HomeworkScoreService ->whereIn('semester', $semVariants) ->where('school_year', $schoolYear) ->get() - ->map(fn ($row) => (array) $row) + ->map(fn ($row) => $row->toArray()) ->all(); $scoresMap = []; foreach ($scoresRows as $row) { - $scoresMap[(int) $row['student_id']][(int) $row['homework_index']] = $row['score']; + $score = $row['score'] ?? null; + $scoresMap[(int) $row['student_id']][(int) $row['homework_index']] = is_numeric($score) ? (float) $score : $score; } foreach ($students as &$student) { @@ -196,6 +197,10 @@ class HomeworkScoreService ->get(); foreach ($students as $student) { + $studentRow = Student::query()->find((int) $student->student_id); + if (!$studentRow) { + continue; + } $exists = Homework::query() ->where('student_id', $student->student_id) ->where('homework_index', $nextIndex) @@ -210,6 +215,7 @@ class HomeworkScoreService Homework::query()->create([ 'student_id' => (int) $student->student_id, + 'school_id' => $studentRow->school_id, 'class_section_id' => $classSectionId, 'updated_by' => $updatedBy, 'homework_index' => $nextIndex, diff --git a/app/Services/Scores/ParticipationScoreService.php b/app/Services/Scores/ParticipationScoreService.php index e474a15a..dadd9e73 100644 --- a/app/Services/Scores/ParticipationScoreService.php +++ b/app/Services/Scores/ParticipationScoreService.php @@ -50,12 +50,14 @@ class ParticipationScoreService $scoreMap = []; foreach ($scoresRows as $row) { - $scoreMap[(int) $row->student_id] = $row->score; + $score = $row->score; + $scoreMap[(int) $row->student_id] = is_numeric($score) ? (float) $score : $score; } foreach ($students as &$student) { + $score = $scoreMap[$student['student_id']] ?? ''; $student['scores'] = [ - 'score' => $scoreMap[$student['student_id']] ?? '', + 'score' => $score, ]; } unset($student); diff --git a/app/Services/Scores/ProjectScoreService.php b/app/Services/Scores/ProjectScoreService.php index 85b43a2d..10e6bffe 100644 --- a/app/Services/Scores/ProjectScoreService.php +++ b/app/Services/Scores/ProjectScoreService.php @@ -57,12 +57,13 @@ class ProjectScoreService ->where('semester', $semester) ->where('school_year', $schoolYear) ->get() - ->map(fn ($row) => (array) $row) + ->map(fn ($row) => $row->toArray()) ->all(); $scoresMap = []; foreach ($scoresRows as $row) { - $scoresMap[(int) $row['student_id']][(int) $row['project_index']] = $row['score']; + $score = $row['score'] ?? null; + $scoresMap[(int) $row['student_id']][(int) $row['project_index']] = is_numeric($score) ? (float) $score : $score; } foreach ($students as &$student) { @@ -193,6 +194,10 @@ class ProjectScoreService ->get(); foreach ($students as $student) { + $studentRow = Student::query()->find((int) $student->student_id); + if (!$studentRow) { + continue; + } $exists = Project::query() ->where('student_id', $student->student_id) ->where('project_index', $nextIndex) @@ -207,6 +212,7 @@ class ProjectScoreService Project::query()->create([ 'student_id' => (int) $student->student_id, + 'school_id' => $studentRow->school_id, 'class_section_id' => $classSectionId, 'updated_by' => $updatedBy, 'project_index' => $nextIndex, diff --git a/app/Services/Scores/QuizScoreService.php b/app/Services/Scores/QuizScoreService.php index 16989e3f..0f567094 100644 --- a/app/Services/Scores/QuizScoreService.php +++ b/app/Services/Scores/QuizScoreService.php @@ -56,12 +56,13 @@ class QuizScoreService ->where('semester', $semester) ->where('school_year', $schoolYear) ->get() - ->map(fn ($row) => (array) $row) + ->map(fn ($row) => $row->toArray()) ->all(); $scoresMap = []; foreach ($scoresRows as $row) { - $scoresMap[(int) $row['student_id']][(int) $row['quiz_index']] = $row['score']; + $score = $row['score'] ?? null; + $scoresMap[(int) $row['student_id']][(int) $row['quiz_index']] = is_numeric($score) ? (float) $score : $score; } foreach ($students as &$student) { @@ -135,6 +136,10 @@ class QuizScoreService if (!is_array($quizData)) { $quizData = [1 => $quizData]; } + $student = Student::query()->find((int) $studentId); + if (!$student) { + continue; + } foreach ($quizData as $quizNumber => $score) { if (!is_numeric($quizNumber)) { continue; @@ -152,6 +157,7 @@ class QuizScoreService $payload = [ 'student_id' => (int) $studentId, + 'school_id' => $student->school_id, 'class_section_id' => $classSectionId, 'updated_by' => $updatedBy, 'quiz_index' => (int) $quizNumber, @@ -190,6 +196,10 @@ class QuizScoreService ->get(); foreach ($students as $student) { + $studentRow = Student::query()->find((int) $student->student_id); + if (!$studentRow) { + continue; + } $exists = Quiz::query() ->where('student_id', $student->student_id) ->where('quiz_index', $nextIndex) @@ -204,6 +214,7 @@ class QuizScoreService Quiz::query()->create([ 'student_id' => (int) $student->student_id, + 'school_id' => $studentRow->school_id, 'class_section_id' => $classSectionId, 'updated_by' => $updatedBy, 'quiz_index' => $nextIndex, diff --git a/app/Services/Scores/ScoreDashboardService.php b/app/Services/Scores/ScoreDashboardService.php index 8f82c51c..99b11b51 100644 --- a/app/Services/Scores/ScoreDashboardService.php +++ b/app/Services/Scores/ScoreDashboardService.php @@ -111,14 +111,14 @@ class ScoreDashboardService $sid = $student['student_id']; $scoreRow = $scores[$sid] ?? null; $student['scores'] = $scoreRow ? [ - 'homework' => $scoreRow->homework_avg, - 'quiz' => $scoreRow->quiz_avg, - 'project' => $scoreRow->project_avg, - 'midterm_exam' => $scoreRow->midterm_exam_score, - 'final_exam' => $scoreRow->final_exam_score, - 'attendance' => $scoreRow->attendance_score, - 'ptap' => $scoreRow->ptap_score, - 'semester' => $scoreRow->semester_score, + 'homework' => $this->normalizeScore($scoreRow->homework_avg), + 'quiz' => $this->normalizeScore($scoreRow->quiz_avg), + 'project' => $this->normalizeScore($scoreRow->project_avg), + 'midterm_exam' => $this->normalizeScore($scoreRow->midterm_exam_score), + 'final_exam' => $this->normalizeScore($scoreRow->final_exam_score), + 'attendance' => $this->normalizeScore($scoreRow->attendance_score), + 'ptap' => $this->normalizeScore($scoreRow->ptap_score), + 'semester' => $this->normalizeScore($scoreRow->semester_score), ] : []; $student['comments'] = $commentMap[$sid] ?? []; } @@ -267,4 +267,13 @@ class ScoreDashboardService 'selectedYear' => $schoolYear, ]; } + + private function normalizeScore(mixed $value): mixed + { + if ($value === null || $value === '') { + return $value; + } + + return is_numeric($value) ? (float) $value : $value; + } } diff --git a/app/Services/Users/UserManagementService.php b/app/Services/Users/UserManagementService.php index f7aaa4e7..2c3911c2 100644 --- a/app/Services/Users/UserManagementService.php +++ b/app/Services/Users/UserManagementService.php @@ -73,7 +73,7 @@ class UserManagementService try { DB::transaction(function () use ($userId, $user) { - UserRole::query()->where('user_id', $userId)->delete(); + UserRole::query()->where('user_id', $userId)->forceDelete(); $user->delete(); }); } catch (\Throwable $e) { diff --git a/app/Http/Controllers/old/ApiClient.php b/app/old/ApiClient.php similarity index 100% rename from app/Http/Controllers/old/ApiClient.php rename to app/old/ApiClient.php diff --git a/app/Http/Controllers/old/ClassController.php b/app/old/ClassController.php similarity index 100% rename from app/Http/Controllers/old/ClassController.php rename to app/old/ClassController.php diff --git a/app/Http/Controllers/old/ConfigurationController.php b/app/old/ConfigurationController.php similarity index 100% rename from app/Http/Controllers/old/ConfigurationController.php rename to app/old/ConfigurationController.php diff --git a/app/Http/Controllers/old/ContactController.php b/app/old/ContactController.php similarity index 100% rename from app/Http/Controllers/old/ContactController.php rename to app/old/ContactController.php diff --git a/app/Http/Controllers/old/DashboardRedirectController.php b/app/old/DashboardRedirectController.php similarity index 100% rename from app/Http/Controllers/old/DashboardRedirectController.php rename to app/old/DashboardRedirectController.php diff --git a/app/Http/Controllers/old/DocsController.php b/app/old/DocsController.php similarity index 100% rename from app/Http/Controllers/old/DocsController.php rename to app/old/DocsController.php diff --git a/app/Http/Controllers/old/EmergencyContactController.php b/app/old/EmergencyContactController.php similarity index 100% rename from app/Http/Controllers/old/EmergencyContactController.php rename to app/old/EmergencyContactController.php diff --git a/app/Http/Controllers/old/EventController.php b/app/old/EventController.php similarity index 100% rename from app/Http/Controllers/old/EventController.php rename to app/old/EventController.php diff --git a/app/Http/Controllers/old/ExamDraftController.php b/app/old/ExamDraftController.php similarity index 100% rename from app/Http/Controllers/old/ExamDraftController.php rename to app/old/ExamDraftController.php diff --git a/app/Http/Controllers/old/FamilyAdminController.php b/app/old/FamilyAdminController.php similarity index 100% rename from app/Http/Controllers/old/FamilyAdminController.php rename to app/old/FamilyAdminController.php diff --git a/app/Http/Controllers/old/FamilyController.php b/app/old/FamilyController.php similarity index 100% rename from app/Http/Controllers/old/FamilyController.php rename to app/old/FamilyController.php diff --git a/app/Http/Controllers/old/FeeCalculationService.php b/app/old/FeeCalculationService.php similarity index 100% rename from app/Http/Controllers/old/FeeCalculationService.php rename to app/old/FeeCalculationService.php diff --git a/app/Http/Controllers/old/FrontendController.php b/app/old/FrontendController.php similarity index 100% rename from app/Http/Controllers/old/FrontendController.php rename to app/old/FrontendController.php diff --git a/app/Http/Controllers/old/HealthController.php b/app/old/HealthController.php similarity index 100% rename from app/Http/Controllers/old/HealthController.php rename to app/old/HealthController.php diff --git a/app/Http/Controllers/old/InfoIconController.php b/app/old/InfoIconController.php similarity index 100% rename from app/Http/Controllers/old/InfoIconController.php rename to app/old/InfoIconController.php diff --git a/app/Http/Controllers/old/InventoryController.php b/app/old/InventoryController.php similarity index 100% rename from app/Http/Controllers/old/InventoryController.php rename to app/old/InventoryController.php diff --git a/app/Http/Controllers/old/IpBanController.php b/app/old/IpBanController.php similarity index 100% rename from app/Http/Controllers/old/IpBanController.php rename to app/old/IpBanController.php diff --git a/app/Http/Controllers/old/LandingPageController.php b/app/old/LandingPageController.php similarity index 100% rename from app/Http/Controllers/old/LandingPageController.php rename to app/old/LandingPageController.php diff --git a/app/Http/Controllers/old/LateSlipLogsController.php b/app/old/LateSlipLogsController.php similarity index 100% rename from app/Http/Controllers/old/LateSlipLogsController.php rename to app/old/LateSlipLogsController.php diff --git a/app/Http/Controllers/old/MessagesController.php b/app/old/MessagesController.php similarity index 100% rename from app/Http/Controllers/old/MessagesController.php rename to app/old/MessagesController.php diff --git a/app/Http/Controllers/old/NavBuilderController.php b/app/old/NavBuilderController.php similarity index 100% rename from app/Http/Controllers/old/NavBuilderController.php rename to app/old/NavBuilderController.php diff --git a/app/Http/Controllers/old/NavbarService.php b/app/old/NavbarService.php similarity index 100% rename from app/Http/Controllers/old/NavbarService.php rename to app/old/NavbarService.php diff --git a/app/Http/Controllers/old/NotificationService.php b/app/old/NotificationService.php similarity index 100% rename from app/Http/Controllers/old/NotificationService.php rename to app/old/NotificationService.php diff --git a/app/Http/Controllers/old/NotificationsController.php b/app/old/NotificationsController.php similarity index 100% rename from app/Http/Controllers/old/NotificationsController.php rename to app/old/NotificationsController.php diff --git a/app/Http/Controllers/old/PageController.php b/app/old/PageController.php similarity index 100% rename from app/Http/Controllers/old/PageController.php rename to app/old/PageController.php diff --git a/app/Http/Controllers/old/ParentAttendanceReportController.php b/app/old/ParentAttendanceReportController.php similarity index 100% rename from app/Http/Controllers/old/ParentAttendanceReportController.php rename to app/old/ParentAttendanceReportController.php diff --git a/app/Http/Controllers/old/ParentController.php b/app/old/ParentController.php similarity index 100% rename from app/Http/Controllers/old/ParentController.php rename to app/old/ParentController.php diff --git a/app/Http/Controllers/old/PhoneFormatterService.php b/app/old/PhoneFormatterService.php similarity index 100% rename from app/Http/Controllers/old/PhoneFormatterService.php rename to app/old/PhoneFormatterService.php diff --git a/app/Http/Controllers/old/PolicyController.php b/app/old/PolicyController.php similarity index 100% rename from app/Http/Controllers/old/PolicyController.php rename to app/old/PolicyController.php diff --git a/app/Http/Controllers/old/PreferencesController.php b/app/old/PreferencesController.php similarity index 100% rename from app/Http/Controllers/old/PreferencesController.php rename to app/old/PreferencesController.php diff --git a/app/Http/Controllers/old/PrintablesBaseController.php b/app/old/PrintablesBaseController.php similarity index 100% rename from app/Http/Controllers/old/PrintablesBaseController.php rename to app/old/PrintablesBaseController.php diff --git a/app/Http/Controllers/old/PurchaseOrderController.php b/app/old/PurchaseOrderController.php similarity index 100% rename from app/Http/Controllers/old/PurchaseOrderController.php rename to app/old/PurchaseOrderController.php diff --git a/app/Http/Controllers/old/RFIDController.php b/app/old/RFIDController.php similarity index 100% rename from app/Http/Controllers/old/RFIDController.php rename to app/old/RFIDController.php diff --git a/app/Http/Controllers/old/RefundController.php b/app/old/RefundController.php similarity index 100% rename from app/Http/Controllers/old/RefundController.php rename to app/old/RefundController.php diff --git a/app/Http/Controllers/old/ReportCardsController.php b/app/old/ReportCardsController.php similarity index 100% rename from app/Http/Controllers/old/ReportCardsController.php rename to app/old/ReportCardsController.php diff --git a/app/Http/Controllers/old/RolePermissionController.php b/app/old/RolePermissionController.php similarity index 100% rename from app/Http/Controllers/old/RolePermissionController.php rename to app/old/RolePermissionController.php diff --git a/app/Http/Controllers/old/RoleService.php b/app/old/RoleService.php similarity index 100% rename from app/Http/Controllers/old/RoleService.php rename to app/old/RoleService.php diff --git a/app/Http/Controllers/old/RoleSwitcherController.php b/app/old/RoleSwitcherController.php similarity index 100% rename from app/Http/Controllers/old/RoleSwitcherController.php rename to app/old/RoleSwitcherController.php diff --git a/app/Http/Controllers/old/SchoolCalendarController.php b/app/old/SchoolCalendarController.php similarity index 100% rename from app/Http/Controllers/old/SchoolCalendarController.php rename to app/old/SchoolCalendarController.php diff --git a/app/Http/Controllers/old/SchoolIdService.php b/app/old/SchoolIdService.php similarity index 100% rename from app/Http/Controllers/old/SchoolIdService.php rename to app/old/SchoolIdService.php diff --git a/app/Http/Controllers/old/SendSMSController.php b/app/old/SendSMSController.php similarity index 100% rename from app/Http/Controllers/old/SendSMSController.php rename to app/old/SendSMSController.php diff --git a/app/Http/Controllers/old/SessionTimeoutController.php b/app/old/SessionTimeoutController.php similarity index 100% rename from app/Http/Controllers/old/SessionTimeoutController.php rename to app/old/SessionTimeoutController.php diff --git a/app/Http/Controllers/old/SettingsController.php b/app/old/SettingsController.php similarity index 100% rename from app/Http/Controllers/old/SettingsController.php rename to app/old/SettingsController.php diff --git a/app/Http/Controllers/old/SlipPrinterController.php b/app/old/SlipPrinterController.php similarity index 100% rename from app/Http/Controllers/old/SlipPrinterController.php rename to app/old/SlipPrinterController.php diff --git a/app/Http/Controllers/old/StaffController.php b/app/old/StaffController.php similarity index 100% rename from app/Http/Controllers/old/StaffController.php rename to app/old/StaffController.php diff --git a/app/Http/Controllers/old/StatsController.php b/app/old/StatsController.php similarity index 100% rename from app/Http/Controllers/old/StatsController.php rename to app/old/StatsController.php diff --git a/app/Http/Controllers/old/StickersController.php b/app/old/StickersController.php similarity index 100% rename from app/Http/Controllers/old/StickersController.php rename to app/old/StickersController.php diff --git a/app/Http/Controllers/old/StudentController.php b/app/old/StudentController.php similarity index 100% rename from app/Http/Controllers/old/StudentController.php rename to app/old/StudentController.php diff --git a/app/Http/Controllers/old/SubjectCurriculumController.php b/app/old/SubjectCurriculumController.php similarity index 100% rename from app/Http/Controllers/old/SubjectCurriculumController.php rename to app/old/SubjectCurriculumController.php diff --git a/app/Http/Controllers/old/SupplierController.php b/app/old/SupplierController.php similarity index 100% rename from app/Http/Controllers/old/SupplierController.php rename to app/old/SupplierController.php diff --git a/app/Http/Controllers/old/SupplyCategoryController.php b/app/old/SupplyCategoryController.php similarity index 100% rename from app/Http/Controllers/old/SupplyCategoryController.php rename to app/old/SupplyCategoryController.php diff --git a/app/Http/Controllers/old/SupportController.php b/app/old/SupportController.php similarity index 100% rename from app/Http/Controllers/old/SupportController.php rename to app/old/SupportController.php diff --git a/app/Http/Controllers/old/TeacherController.php b/app/old/TeacherController.php similarity index 100% rename from app/Http/Controllers/old/TeacherController.php rename to app/old/TeacherController.php diff --git a/app/Http/Controllers/old/TestDBController.php b/app/old/TestDBController.php similarity index 100% rename from app/Http/Controllers/old/TestDBController.php rename to app/old/TestDBController.php diff --git a/app/Http/Controllers/old/TimeService.php b/app/old/TimeService.php similarity index 100% rename from app/Http/Controllers/old/TimeService.php rename to app/old/TimeService.php diff --git a/app/Http/Controllers/old/UiController.php b/app/old/UiController.php similarity index 100% rename from app/Http/Controllers/old/UiController.php rename to app/old/UiController.php diff --git a/app/Http/Controllers/old/WhatsappController.php b/app/old/WhatsappController.php similarity index 100% rename from app/Http/Controllers/old/WhatsappController.php rename to app/old/WhatsappController.php diff --git a/database/factories/ClassSectionFactory.php b/database/factories/ClassSectionFactory.php new file mode 100644 index 00000000..69a733bd --- /dev/null +++ b/database/factories/ClassSectionFactory.php @@ -0,0 +1,27 @@ + + */ +class ClassSectionFactory extends Factory +{ + protected $model = ClassSection::class; + + public function definition(): array + { + $sectionId = fake()->numberBetween(1, 999); + + return [ + 'class_id' => fake()->numberBetween(1, 20), + 'class_section_id' => $sectionId, + 'class_section_name' => (string) $sectionId . fake()->randomElement(['A', 'B', 'C']), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]; + } +} diff --git a/database/factories/StudentFactory.php b/database/factories/StudentFactory.php new file mode 100644 index 00000000..e50def9f --- /dev/null +++ b/database/factories/StudentFactory.php @@ -0,0 +1,34 @@ + + */ +class StudentFactory extends Factory +{ + protected $model = Student::class; + + public function definition(): array + { + return [ + 'school_id' => 'S-' . fake()->numberBetween(100, 999), + 'firstname' => fake()->firstName(), + 'lastname' => fake()->lastName(), + 'age' => fake()->numberBetween(6, 18), + 'gender' => fake()->randomElement(['Male', 'Female']), + 'is_active' => 1, + 'registration_grade' => 'Grade ' . fake()->numberBetween(1, 8), + 'is_new' => 1, + 'photo_consent' => 1, + 'parent_id' => fake()->numberBetween(1, 50), + 'tuition_paid' => 0, + 'semester' => 'Fall', + 'year_of_registration' => '2025', + 'school_year' => '2025-2026', + ]; + } +} diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 584104c9..5212a445 100755 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -24,11 +24,20 @@ class UserFactory extends Factory public function definition(): array { return [ - 'name' => fake()->name(), + 'school_id' => fake()->numberBetween(1, 5), + 'firstname' => fake()->firstName(), + 'lastname' => fake()->lastName(), + 'gender' => fake()->randomElement(['Male', 'Female']), + 'cellphone' => fake()->numerify('##########'), 'email' => fake()->unique()->safeEmail(), - 'email_verified_at' => now(), + 'address_street' => fake()->streetAddress(), + 'city' => fake()->city(), + 'state' => fake()->stateAbbr(), + 'zip' => fake()->postcode(), + 'accept_school_policy' => 1, + 'semester' => 'Fall', + 'school_year' => '2025-2026', 'password' => static::$password ??= Hash::make('password'), - 'remember_token' => Str::random(10), ]; } @@ -37,8 +46,6 @@ class UserFactory extends Factory */ public function unverified(): static { - return $this->state(fn (array $attributes) => [ - 'email_verified_at' => null, - ]); + return $this->state(fn (array $attributes) => $attributes); } } diff --git a/database/migrations/2026_02_23_900031_create_event_charges_table.php b/database/migrations/2026_02_23_900031_create_event_charges_table.php index b19e7ffb..4d90cf2f 100644 --- a/database/migrations/2026_02_23_900031_create_event_charges_table.php +++ b/database/migrations/2026_02_23_900031_create_event_charges_table.php @@ -11,7 +11,10 @@ return new class extends Migration App\Support\SqliteCompat::statement(<<<'SQL' CREATE TABLE `event_charges` ( `id` int UNSIGNED NOT NULL, - `event_id` int UNSIGNED NOT NULL, + `event_id` int UNSIGNED DEFAULT NULL, + `event_name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + `description` text COLLATE utf8mb4_general_ci, + `amount` decimal(10,2) DEFAULT NULL, `parent_id` int UNSIGNED DEFAULT NULL, `student_id` int UNSIGNED DEFAULT NULL, `participation` enum('yes','no') COLLATE utf8mb4_general_ci DEFAULT NULL, @@ -43,4 +46,4 @@ SQL { Schema::dropIfExists('event_charges'); } -}; \ No newline at end of file +}; diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml index a6b6eb74..827755be 100644 --- a/docker-compose.ci.yml +++ b/docker-compose.ci.yml @@ -3,15 +3,19 @@ services: build: context: . dockerfile: Dockerfile.ci + container_name: alrahma_ci_app working_dir: /app volumes: - - ${WORKSPACE:-.}:/app + - .:/app environment: APP_ENV: testing APP_KEY: base64:testingKey123456789012345678901234567890= + APP_KEY: base64:testingKey123456789012345678901234567890= DB_CONNECTION: sqlite DB_DATABASE: /app/database/database.sqlite + DB_DATABASE: /app/database/database.sqlite CACHE_DRIVER: array QUEUE_CONNECTION: sync SESSION_DRIVER: array - MAIL_MAILER: log \ No newline at end of file + MAIL_MAILER: log + command: sh -lc "ls -la && test -f composer.json && composer install --no-interaction --prefer-dist --no-progress" diff --git a/school_api b/school_api index de94e1d8..93afb4d7 100644 Binary files a/school_api and b/school_api differ diff --git a/school_api_test b/school_api_test deleted file mode 100644 index 073f30de..00000000 Binary files a/school_api_test and /dev/null differ diff --git a/tests/Feature/Api/V1/Administrator/AdministratorAbsenceControllerTest.php b/tests/Feature/Api/V1/Administrator/AdministratorAbsenceControllerTest.php index 6e51b083..cc354eab 100644 --- a/tests/Feature/Api/V1/Administrator/AdministratorAbsenceControllerTest.php +++ b/tests/Feature/Api/V1/Administrator/AdministratorAbsenceControllerTest.php @@ -21,7 +21,7 @@ class AdministratorAbsenceControllerTest extends TestCase public function test_guest_cannot_access_absence_index(): void { - $response = $this->getJson('/api/administrator/absence'); + $response = $this->getJson('/api/v1/administrator/absence'); $response->assertStatus(401); } @@ -46,7 +46,7 @@ class AdministratorAbsenceControllerTest extends TestCase $this->app->instance(AdministratorAbsenceService::class, $mock); - $response = $this->getJson('/api/administrator/absence'); + $response = $this->getJson('/api/v1/administrator/absence'); $response->assertOk() ->assertJson([ @@ -62,7 +62,7 @@ class AdministratorAbsenceControllerTest extends TestCase Sanctum::actingAs($user); - $response = $this->postJson('/api/administrator/absence', [ + $response = $this->postJson('/api/v1/administrator/absence', [ 'dates' => ['2026-03-08'], 'reason_type' => 'vacation', 'reason' => '', @@ -78,7 +78,7 @@ class AdministratorAbsenceControllerTest extends TestCase Sanctum::actingAs($user); - $response = $this->postJson('/api/administrator/absence', [ + $response = $this->postJson('/api/v1/administrator/absence', [ 'dates' => ['03/08/2026'], 'reason_type' => 'vacation', 'reason' => 'Family trip', @@ -112,7 +112,7 @@ class AdministratorAbsenceControllerTest extends TestCase $this->app->instance(AdministratorAbsenceService::class, $mock); - $response = $this->postJson('/api/administrator/absence', $payload); + $response = $this->postJson('/api/v1/administrator/absence', $payload); $response->assertOk() ->assertJson([ diff --git a/tests/Feature/Api/V1/Administrator/AdministratorEnrollmentControllerTest.php b/tests/Feature/Api/V1/Administrator/AdministratorEnrollmentControllerTest.php index 275c14c6..85c81f08 100644 --- a/tests/Feature/Api/V1/Administrator/AdministratorEnrollmentControllerTest.php +++ b/tests/Feature/Api/V1/Administrator/AdministratorEnrollmentControllerTest.php @@ -22,7 +22,7 @@ class AdministratorEnrollmentControllerTest extends TestCase public function test_guest_cannot_update_enrollment_statuses(): void { - $response = $this->postJson('/api/administrator/enrollment-withdrawal/update-statuses', [ + $response = $this->postJson('/api/v1/administrator/enrollment-withdrawal/update-statuses', [ 'enrollment_status' => [ 10 => 'enrolled', ], @@ -50,7 +50,7 @@ class AdministratorEnrollmentControllerTest extends TestCase $this->app->instance(AdministratorEnrollmentQueryService::class, $mock); - $response = $this->getJson('/api/administrator/enrollment-withdrawal?schoolYear=2025-2026'); + $response = $this->getJson('/api/v1/administrator/enrollment-withdrawal?schoolYear=2025-2026'); $response->assertOk() ->assertJson([ @@ -74,7 +74,7 @@ class AdministratorEnrollmentControllerTest extends TestCase $this->app->instance(AdministratorEnrollmentQueryService::class, $mock); - $response = $this->getJson('/api/administrator/enrollment-withdrawal/new-students'); + $response = $this->getJson('/api/v1/administrator/enrollment-withdrawal/new-students'); $response->assertOk() ->assertJson([ @@ -88,7 +88,7 @@ class AdministratorEnrollmentControllerTest extends TestCase $user = User::factory()->create(); Sanctum::actingAs($user); - $response = $this->postJson('/api/administrator/enrollment-withdrawal/update-statuses', [ + $response = $this->postJson('/api/v1/administrator/enrollment-withdrawal/update-statuses', [ 'enrollment_status' => [ 10 => 'bad-status', ], @@ -121,7 +121,7 @@ class AdministratorEnrollmentControllerTest extends TestCase $this->app->instance(AdministratorEnrollmentStatusService::class, $mock); - $response = $this->postJson('/api/administrator/enrollment-withdrawal/update-statuses', $payload); + $response = $this->postJson('/api/v1/administrator/enrollment-withdrawal/update-statuses', $payload); $response->assertOk() ->assertJson([ diff --git a/tests/Feature/Api/V1/Administrator/AdministratorEnrollmentQueryApiTest.php b/tests/Feature/Api/V1/Administrator/AdministratorEnrollmentQueryApiTest.php index f441346b..4274e8f6 100644 --- a/tests/Feature/Api/V1/Administrator/AdministratorEnrollmentQueryApiTest.php +++ b/tests/Feature/Api/V1/Administrator/AdministratorEnrollmentQueryApiTest.php @@ -48,30 +48,29 @@ class AdministratorEnrollmentQueryApiTest extends TestCase 'lastname' => 'Two', ]); - $studentOne = Student::query()->create([ + $studentOne = Student::factory()->create([ 'id' => 1, 'parent_id' => $parentOne->id, 'firstname' => 'Ali', 'lastname' => 'Ben', 'is_new' => 1, 'registration_date' => '2025-09-10 08:00:00', - 'admission_status' => 'accepted', ]); - $studentTwo = Student::query()->create([ + $studentTwo = Student::factory()->create([ 'id' => 2, 'parent_id' => $parentTwo->id, 'firstname' => 'Sara', 'lastname' => 'Ali', 'is_new' => 0, 'registration_date' => '2024-09-10 08:00:00', - 'admission_status' => 'accepted', ]); ClassSection::unguard(); ClassSection::query()->create([ 'class_section_id' => 10, 'class_section_name' => 'Grade 5A', + 'class_id' => 5, 'school_year' => '2025-2026', 'semester' => 'Fall', ]); @@ -107,7 +106,7 @@ class AdministratorEnrollmentQueryApiTest extends TestCase 'enrollment_date' => now()->subYear()->toDateString(), ]); - $response = $this->getJson('/api/administrator/enrollment-withdrawal?schoolYear=2025-2026'); + $response = $this->getJson('/api/v1/administrator/enrollment-withdrawal?schoolYear=2025-2026'); $response->assertOk() ->assertJson([ @@ -142,30 +141,29 @@ class AdministratorEnrollmentQueryApiTest extends TestCase 'lastname' => 'House', ]); - $studentOne = Student::query()->create([ + $studentOne = Student::factory()->create([ 'id' => 9, 'parent_id' => $parent->id, 'firstname' => 'Adam', 'lastname' => 'Y', 'is_new' => 1, 'registration_date' => '2025-10-01 10:00:00', - 'admission_status' => 'accepted', ]); - $studentTwo = Student::query()->create([ + $studentTwo = Student::factory()->create([ 'id' => 10, 'parent_id' => $parent->id, 'firstname' => 'Eve', 'lastname' => 'Z', 'is_new' => 0, 'registration_date' => null, - 'admission_status' => 'accepted', ]); ClassSection::unguard(); ClassSection::query()->create([ 'class_section_id' => 20, 'class_section_name' => 'Grade 1A', + 'class_id' => 1, 'school_year' => '2025-2026', 'semester' => 'Fall', ]); @@ -201,7 +199,7 @@ class AdministratorEnrollmentQueryApiTest extends TestCase 'enrollment_date' => now()->toDateString(), ]); - $response = $this->getJson('/api/administrator/enrollment-withdrawal/new-students'); + $response = $this->getJson('/api/v1/administrator/enrollment-withdrawal/new-students'); $response->assertOk() ->assertJson([ @@ -224,4 +222,4 @@ class AdministratorEnrollmentQueryApiTest extends TestCase $this->assertSame('Class not Assigned', $second['class_section']); $this->assertSame('waitlist', $second['enrollment_status']); } -} \ No newline at end of file +} diff --git a/tests/Feature/Api/V1/Administrator/AdministratorEnrollmentUpdateStatusesApiTest.php b/tests/Feature/Api/V1/Administrator/AdministratorEnrollmentUpdateStatusesApiTest.php index e581eeb0..15744d66 100644 --- a/tests/Feature/Api/V1/Administrator/AdministratorEnrollmentUpdateStatusesApiTest.php +++ b/tests/Feature/Api/V1/Administrator/AdministratorEnrollmentUpdateStatusesApiTest.php @@ -5,7 +5,7 @@ namespace Tests\Feature\Api\Administrator; use App\Models\Configuration; use App\Models\Enrollment; use App\Models\Invoice; -use App\Models\RefundModel as Refund; +use App\Models\Refund; use App\Models\Student; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; @@ -37,7 +37,7 @@ class AdministratorEnrollmentUpdateStatusesApiTest extends TestCase $admin = User::factory()->create(); Sanctum::actingAs($admin); - $response = $this->postJson('/api/administrator/enrollment-withdrawal/update-statuses', []); + $response = $this->postUpdateStatuses([]); $response->assertStatus(422) ->assertJsonValidationErrors(['enrollment_status']); @@ -48,14 +48,10 @@ class AdministratorEnrollmentUpdateStatusesApiTest extends TestCase $admin = User::factory()->create(); Sanctum::actingAs($admin); - $response = $this->postJson('/api/administrator/enrollment-withdrawal/update-statuses', [ - 'enrollment_status' => [ - 10 => 'not-valid', - ], - ]); + $response = $this->postUpdateStatuses(['not-valid']); $response->assertStatus(422) - ->assertJsonValidationErrors(['enrollment_status.10']); + ->assertJsonValidationErrors(['enrollment_status.0']); } public function test_update_statuses_creates_new_enrollment_row(): void @@ -72,19 +68,15 @@ class AdministratorEnrollmentUpdateStatusesApiTest extends TestCase 'email' => 'parent1@test.com', ]); - Student::unguard(); - Student::query()->create([ + Student::factory()->create([ 'id' => 10, 'parent_id' => $parent->id, 'firstname' => 'Ali', 'lastname' => 'Ben', - 'admission_status' => 'pending', ]); - $response = $this->postJson('/api/administrator/enrollment-withdrawal/update-statuses', [ - 'enrollment_status' => [ - 10 => 'enrolled', - ], + $response = $this->postUpdateStatuses([ + 10 => 'enrolled', ]); $response->assertOk() @@ -117,13 +109,11 @@ class AdministratorEnrollmentUpdateStatusesApiTest extends TestCase 'email' => 'parent2@test.com', ]); - Student::unguard(); - Student::query()->create([ + Student::factory()->create([ 'id' => 11, 'parent_id' => $parent->id, 'firstname' => 'Sara', 'lastname' => 'Ali', - 'admission_status' => 'pending', ]); Enrollment::unguard(); @@ -138,10 +128,8 @@ class AdministratorEnrollmentUpdateStatusesApiTest extends TestCase 'enrollment_date' => now()->toDateString(), ]); - $response = $this->postJson('/api/administrator/enrollment-withdrawal/update-statuses', [ - 'enrollment_status' => [ - 11 => 'withdrawn', - ], + $response = $this->postUpdateStatuses([ + 11 => 'withdrawn', ]); $response->assertOk(); @@ -169,13 +157,11 @@ class AdministratorEnrollmentUpdateStatusesApiTest extends TestCase 'email' => 'parent3@test.com', ]); - Student::unguard(); - Student::query()->create([ + Student::factory()->create([ 'id' => 12, 'parent_id' => $parent->id, 'firstname' => 'Mina', 'lastname' => 'K', - 'admission_status' => 'accepted', ]); Enrollment::unguard(); @@ -194,16 +180,15 @@ class AdministratorEnrollmentUpdateStatusesApiTest extends TestCase $invoice = Invoice::query()->create([ 'parent_id' => 102, 'school_year' => '2025-2026', + 'invoice_number' => 'INV-REF-1', 'balance' => 125.00, - 'amount_due' => 125.00, 'total_amount' => 125.00, + 'issue_date' => now()->toDateString(), 'due_date' => now()->addDays(10)->toDateString(), ]); - $response = $this->postJson('/api/administrator/enrollment-withdrawal/update-statuses', [ - 'enrollment_status' => [ - 12 => 'refund pending', - ], + $response = $this->postUpdateStatuses([ + 12 => 'refund pending', ]); $response->assertOk(); @@ -241,28 +226,23 @@ class AdministratorEnrollmentUpdateStatusesApiTest extends TestCase 'email' => 'parent4@test.com', ]); - Student::unguard(); - Student::query()->create([ + Student::factory()->create([ 'id' => 13, 'parent_id' => $parent->id, 'firstname' => 'Adam', 'lastname' => 'Q', - 'admission_status' => 'pending', ]); - Student::query()->create([ + Student::factory()->create([ 'id' => 14, - 'parent_id' => null, + 'parent_id' => 0, 'firstname' => 'No', 'lastname' => 'Parent', - 'admission_status' => 'pending', ]); - $response = $this->postJson('/api/administrator/enrollment-withdrawal/update-statuses', [ - 'enrollment_status' => [ - 13 => 'enrolled', - 14 => 'enrolled', - ], + $response = $this->postUpdateStatuses([ + 13 => 'enrolled', + 14 => 'enrolled', ]); $response->assertStatus(207); @@ -279,4 +259,10 @@ class AdministratorEnrollmentUpdateStatusesApiTest extends TestCase 'school_year' => '2025-2026', ]); } -} \ No newline at end of file + + private function postUpdateStatuses(array $enrollmentStatus) + { + $query = http_build_query(['enrollment_status' => $enrollmentStatus]); + return $this->post('/api/v1/administrator/enrollment-withdrawal/update-statuses?' . $query, []); + } +} diff --git a/tests/Feature/Api/V1/Administrator/AdministratorNotificationControllerTest.php b/tests/Feature/Api/V1/Administrator/AdministratorNotificationControllerTest.php index e825389a..136c57e6 100644 --- a/tests/Feature/Api/V1/Administrator/AdministratorNotificationControllerTest.php +++ b/tests/Feature/Api/V1/Administrator/AdministratorNotificationControllerTest.php @@ -39,7 +39,7 @@ class AdministratorNotificationControllerTest extends TestCase $this->app->instance(AdminNotificationSubjectService::class, $mock); - $response = $this->getJson('/api/administrator/notifications/alerts'); + $response = $this->getJson('/api/v1/administrator/notifications/alerts'); $response->assertOk() ->assertJson([ @@ -54,7 +54,7 @@ class AdministratorNotificationControllerTest extends TestCase $user = User::factory()->create(); Sanctum::actingAs($user); - $response = $this->postJson('/api/administrator/notifications/alerts', []); + $response = $this->postJson('/api/v1/administrator/notifications/alerts', []); $response->assertStatus(422) ->assertJsonValidationErrors(['subjects']); @@ -82,7 +82,7 @@ class AdministratorNotificationControllerTest extends TestCase $this->app->instance(AdminNotificationSubjectService::class, $mock); - $response = $this->postJson('/api/administrator/notifications/alerts', $payload); + $response = $this->postJson('/api/v1/administrator/notifications/alerts', $payload); $response->assertOk() ->assertJson([ @@ -106,7 +106,7 @@ class AdministratorNotificationControllerTest extends TestCase $this->app->instance(AdminPrintRecipientService::class, $mock); - $response = $this->getJson('/api/administrator/notifications/print-recipients'); + $response = $this->getJson('/api/v1/administrator/notifications/print-recipients'); $response->assertOk() ->assertJson([ @@ -121,7 +121,7 @@ class AdministratorNotificationControllerTest extends TestCase $user = User::factory()->create(); Sanctum::actingAs($user); - $response = $this->postJson('/api/administrator/notifications/print-recipients', []); + $response = $this->postJson('/api/v1/administrator/notifications/print-recipients', []); $response->assertStatus(422) ->assertJsonValidationErrors(['notify']); @@ -150,7 +150,7 @@ class AdministratorNotificationControllerTest extends TestCase $this->app->instance(AdminPrintRecipientService::class, $mock); - $response = $this->postJson('/api/administrator/notifications/print-recipients', $payload); + $response = $this->postJson('/api/v1/administrator/notifications/print-recipients', $payload); $response->assertOk() ->assertJson([ diff --git a/tests/Feature/Api/V1/Administrator/AdministratorTeacherSubmissionControllerTest.php b/tests/Feature/Api/V1/Administrator/AdministratorTeacherSubmissionControllerTest.php index 238151e9..7330aee5 100644 --- a/tests/Feature/Api/V1/Administrator/AdministratorTeacherSubmissionControllerTest.php +++ b/tests/Feature/Api/V1/Administrator/AdministratorTeacherSubmissionControllerTest.php @@ -43,7 +43,7 @@ class AdministratorTeacherSubmissionControllerTest extends TestCase $this->app->instance(TeacherSubmissionReportService::class, $mock); - $response = $this->getJson('/api/administrator/teacher-submissions'); + $response = $this->getJson('/api/v1/administrator/teacher-submissions'); $response->assertOk() ->assertJson([ @@ -58,7 +58,7 @@ class AdministratorTeacherSubmissionControllerTest extends TestCase $user = User::factory()->create(); Sanctum::actingAs($user); - $response = $this->postJson('/api/administrator/teacher-submissions/notify', [ + $response = $this->postJson('/api/v1/administrator/teacher-submissions/notify', [ 'missing_items' => [], ]); @@ -96,7 +96,7 @@ class AdministratorTeacherSubmissionControllerTest extends TestCase $this->app->instance(TeacherSubmissionNotificationService::class, $mock); - $response = $this->postJson('/api/administrator/teacher-submissions/notify', $payload); + $response = $this->postJson('/api/v1/administrator/teacher-submissions/notify', $payload); $response->assertOk() ->assertJson([ diff --git a/tests/Feature/Api/V1/Administrator/AdministratorTeacherSubmissionNotifyApiTest.php b/tests/Feature/Api/V1/Administrator/AdministratorTeacherSubmissionNotifyApiTest.php index ad4fd78a..cd2a7d4d 100644 --- a/tests/Feature/Api/V1/Administrator/AdministratorTeacherSubmissionNotifyApiTest.php +++ b/tests/Feature/Api/V1/Administrator/AdministratorTeacherSubmissionNotifyApiTest.php @@ -35,7 +35,7 @@ class AdministratorTeacherSubmissionNotifyApiTest extends TestCase $admin = User::factory()->create(); Sanctum::actingAs($admin); - $response = $this->postJson('/api/administrator/teacher-submissions/notify', [ + $response = $this->postJson('/api/v1/administrator/teacher-submissions/notify', [ 'missing_items' => [], ]); @@ -55,19 +55,20 @@ class AdministratorTeacherSubmissionNotifyApiTest extends TestCase ]); Sanctum::actingAs($admin); - DB::table('users')->insert([ - [ - 'id' => 100, - 'firstname' => 'John', - 'lastname' => 'Teacher', - 'email' => 'teacher@test.com', - ], + User::factory()->create([ + 'id' => 100, + 'firstname' => 'John', + 'lastname' => 'Teacher', + 'email' => 'teacher@test.com', ]); DB::table('classSection')->insert([ [ 'class_section_id' => 10, 'class_section_name' => 'Grade 5A', + 'class_id' => 5, + 'semester' => 'Fall', + 'school_year' => '2025-2026', ], ]); @@ -87,7 +88,7 @@ class AdministratorTeacherSubmissionNotifyApiTest extends TestCase ], ]; - $response = $this->postJson('/api/administrator/teacher-submissions/notify', $payload); + $response = $this->postJson('/api/v1/administrator/teacher-submissions/notify', $payload); $response->assertOk() ->assertJson([ @@ -123,19 +124,20 @@ class AdministratorTeacherSubmissionNotifyApiTest extends TestCase ]); Sanctum::actingAs($admin); - DB::table('users')->insert([ - [ - 'id' => 101, - 'firstname' => 'Bad', - 'lastname' => 'Email', - 'email' => 'not-an-email', - ], + User::factory()->create([ + 'id' => 101, + 'firstname' => 'Bad', + 'lastname' => 'Email', + 'email' => 'not-an-email', ]); DB::table('classSection')->insert([ [ 'class_section_id' => 11, 'class_section_name' => 'Grade 6A', + 'class_id' => 6, + 'semester' => 'Fall', + 'school_year' => '2025-2026', ], ]); @@ -147,7 +149,7 @@ class AdministratorTeacherSubmissionNotifyApiTest extends TestCase ], ]; - $response = $this->postJson('/api/administrator/teacher-submissions/notify', $payload); + $response = $this->postJson('/api/v1/administrator/teacher-submissions/notify', $payload); $response->assertStatus(207) ->assertJson([ @@ -167,4 +169,4 @@ class AdministratorTeacherSubmissionNotifyApiTest extends TestCase 'semester' => 'Fall', ]); } -} \ No newline at end of file +} diff --git a/tests/Feature/Api/V1/Administrator/AdministratorTeacherSubmissionReportApiTest.php b/tests/Feature/Api/V1/Administrator/AdministratorTeacherSubmissionReportApiTest.php index e06ed25c..b15bab50 100644 --- a/tests/Feature/Api/V1/Administrator/AdministratorTeacherSubmissionReportApiTest.php +++ b/tests/Feature/Api/V1/Administrator/AdministratorTeacherSubmissionReportApiTest.php @@ -10,6 +10,7 @@ use App\Models\TeacherSubmissionNotificationHistory; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Str; use Laravel\Sanctum\Sanctum; use Tests\TestCase; @@ -37,25 +38,26 @@ class AdministratorTeacherSubmissionReportApiTest extends TestCase $admin = User::factory()->create(['id' => 900, 'firstname' => 'Admin', 'lastname' => 'One']); Sanctum::actingAs($admin); - DB::table('users')->insert([ - [ - 'id' => 100, - 'firstname' => 'Main', - 'lastname' => 'Teacher', - 'email' => 'main@test.com', - ], - [ - 'id' => 101, - 'firstname' => 'TA', - 'lastname' => 'Teacher', - 'email' => 'ta@test.com', - ], + User::factory()->create([ + 'id' => 100, + 'firstname' => 'Main', + 'lastname' => 'Teacher', + 'email' => 'main@test.com', + ]); + User::factory()->create([ + 'id' => 101, + 'firstname' => 'TA', + 'lastname' => 'Teacher', + 'email' => 'ta@test.com', ]); DB::table('classSection')->insert([ [ 'class_section_id' => 10, 'class_section_name' => 'Grade 5A', + 'class_id' => 5, + 'semester' => 'Fall', + 'school_year' => '2025-2026', ], ]); @@ -91,10 +93,12 @@ class AdministratorTeacherSubmissionReportApiTest extends TestCase ], ]); + $schoolId = (string) Str::uuid(); SemesterScore::unguard(); SemesterScore::query()->create([ 'student_id' => 1, 'class_section_id' => 10, + 'school_id' => $schoolId, 'school_year' => '2025-2026', 'semester' => 'Fall', 'midterm_exam_score' => '88', @@ -103,15 +107,17 @@ class AdministratorTeacherSubmissionReportApiTest extends TestCase SemesterScore::query()->create([ 'student_id' => 2, 'class_section_id' => 10, + 'school_id' => $schoolId, 'school_year' => '2025-2026', 'semester' => 'Fall', - 'midterm_exam_score' => '', + 'midterm_exam_score' => null, 'participation_score' => '9', ]); ScoreComment::unguard(); ScoreComment::query()->create([ 'student_id' => 1, + 'class_section_id' => 10, 'school_year' => '2025-2026', 'semester' => 'Fall', 'score_type' => 'midterm', @@ -119,6 +125,7 @@ class AdministratorTeacherSubmissionReportApiTest extends TestCase ]); ScoreComment::query()->create([ 'student_id' => 1, + 'class_section_id' => 10, 'school_year' => '2025-2026', 'semester' => 'Fall', 'score_type' => 'ptap', @@ -147,7 +154,7 @@ class AdministratorTeacherSubmissionReportApiTest extends TestCase 'sent_at' => now(), ]); - $response = $this->getJson('/api/administrator/teacher-submissions'); + $response = $this->getJson('/api/v1/administrator/teacher-submissions'); $response->assertOk() ->assertJson([ @@ -195,19 +202,20 @@ class AdministratorTeacherSubmissionReportApiTest extends TestCase $admin = User::factory()->create(); Sanctum::actingAs($admin); - DB::table('users')->insert([ - [ - 'id' => 200, - 'firstname' => 'Solo', - 'lastname' => 'Teacher', - 'email' => 'solo@test.com', - ], + User::factory()->create([ + 'id' => 200, + 'firstname' => 'Solo', + 'lastname' => 'Teacher', + 'email' => 'solo@test.com', ]); DB::table('classSection')->insert([ [ 'class_section_id' => 11, 'class_section_name' => 'Grade 6A', + 'class_id' => 6, + 'semester' => 'Fall', + 'school_year' => '2025-2026', ], ]); @@ -221,7 +229,7 @@ class AdministratorTeacherSubmissionReportApiTest extends TestCase ], ]); - $response = $this->getJson('/api/administrator/teacher-submissions'); + $response = $this->getJson('/api/v1/administrator/teacher-submissions'); $response->assertOk() ->assertJsonCount(1, 'rows'); @@ -241,19 +249,20 @@ class AdministratorTeacherSubmissionReportApiTest extends TestCase $admin = User::factory()->create(['id' => 901, 'firstname' => 'Admin', 'lastname' => 'Two']); Sanctum::actingAs($admin); - DB::table('users')->insert([ - [ - 'id' => 300, - 'firstname' => 'Hist', - 'lastname' => 'Teacher', - 'email' => 'hist@test.com', - ], + User::factory()->create([ + 'id' => 300, + 'firstname' => 'Hist', + 'lastname' => 'Teacher', + 'email' => 'hist@test.com', ]); DB::table('classSection')->insert([ [ 'class_section_id' => 12, 'class_section_name' => 'Grade 7A', + 'class_id' => 7, + 'semester' => 'Fall', + 'school_year' => '2025-2026', ], ]); @@ -282,11 +291,11 @@ class AdministratorTeacherSubmissionReportApiTest extends TestCase ]); } - $response = $this->getJson('/api/administrator/teacher-submissions'); + $response = $this->getJson('/api/v1/administrator/teacher-submissions'); $response->assertOk(); $history = $response->json('notificationHistory.12.300'); $this->assertCount(3, $history); } -} \ No newline at end of file +} diff --git a/tests/Feature/Api/V1/Assignment/AssignmentApiControllerTest.php b/tests/Feature/Api/V1/Assignment/AssignmentApiControllerTest.php index 02406a64..3a37da73 100644 --- a/tests/Feature/Api/V1/Assignment/AssignmentApiControllerTest.php +++ b/tests/Feature/Api/V1/Assignment/AssignmentApiControllerTest.php @@ -89,7 +89,7 @@ class AssignmentApiControllerTest extends TestCase { TeacherClass::query()->create([ 'teacher_id' => $this->teacherMain->id, - 'class_section_id' => $this->sectionA->id, + 'class_section_id' => $this->sectionA->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2025-2026', @@ -98,7 +98,7 @@ class AssignmentApiControllerTest extends TestCase TeacherClass::query()->create([ 'teacher_id' => $this->teacherAssistant->id, - 'class_section_id' => $this->sectionA->id, + 'class_section_id' => $this->sectionA->class_section_id, 'position' => 'ta', 'semester' => 'Fall', 'school_year' => '2025-2026', @@ -107,7 +107,7 @@ class AssignmentApiControllerTest extends TestCase StudentClass::query()->create([ 'student_id' => $this->studentOne->id, - 'class_section_id' => $this->sectionA->id, + 'class_section_id' => $this->sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Morning group', @@ -125,7 +125,7 @@ class AssignmentApiControllerTest extends TestCase ->assertJsonPath('data.semester', 'Fall') ->assertJsonPath('data.current_school_year', '2025-2026') ->assertJsonCount(1, 'data.classSections') - ->assertJsonPath('data.classSections.0.class_section_id', $this->sectionA->id) + ->assertJsonPath('data.classSections.0.class_section_id', $this->sectionA->class_section_id) ->assertJsonPath('data.classSections.0.class_section_name', 'Grade 1 - A') ->assertJsonPath('data.classSections.0.main_teachers.0', 'Main Teacher') ->assertJsonPath('data.classSections.0.teacher_assistants.0', 'Assistant Teacher') @@ -140,7 +140,7 @@ class AssignmentApiControllerTest extends TestCase { TeacherClass::query()->create([ 'teacher_id' => $this->teacherMain->id, - 'class_section_id' => $this->sectionA->id, + 'class_section_id' => $this->sectionA->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2025-2026', @@ -149,7 +149,7 @@ class AssignmentApiControllerTest extends TestCase TeacherClass::query()->create([ 'teacher_id' => $this->teacherAssistant->id, - 'class_section_id' => $this->sectionB->id, + 'class_section_id' => $this->sectionB->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2024-2025', @@ -158,7 +158,7 @@ class AssignmentApiControllerTest extends TestCase StudentClass::query()->create([ 'student_id' => $this->studentOne->id, - 'class_section_id' => $this->sectionA->id, + 'class_section_id' => $this->sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Included', @@ -167,7 +167,7 @@ class AssignmentApiControllerTest extends TestCase StudentClass::query()->create([ 'student_id' => $this->studentTwo->id, - 'class_section_id' => $this->sectionB->id, + 'class_section_id' => $this->sectionB->class_section_id, 'semester' => 'Fall', 'school_year' => '2024-2025', 'description' => 'Excluded', @@ -186,7 +186,7 @@ class AssignmentApiControllerTest extends TestCase { TeacherClass::query()->create([ 'teacher_id' => $this->teacherMain->id, - 'class_section_id' => $this->sectionB->id, + 'class_section_id' => $this->sectionB->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2025-2026', @@ -195,7 +195,7 @@ class AssignmentApiControllerTest extends TestCase TeacherClass::query()->create([ 'teacher_id' => $this->teacherAssistant->id, - 'class_section_id' => $this->sectionA->id, + 'class_section_id' => $this->sectionA->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2025-2026', @@ -224,7 +224,7 @@ class AssignmentApiControllerTest extends TestCase TeacherClass::query()->create([ 'teacher_id' => $this->teacherMain->id, - 'class_section_id' => $this->sectionA->id, + 'class_section_id' => $this->sectionA->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2025-2026', @@ -233,7 +233,7 @@ class AssignmentApiControllerTest extends TestCase StudentClass::query()->create([ 'student_id' => $inactiveStudent->id, - 'class_section_id' => $this->sectionA->id, + 'class_section_id' => $this->sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Should not show', @@ -251,7 +251,7 @@ class AssignmentApiControllerTest extends TestCase { $payload = [ 'student_id' => $this->studentOne->id, - 'class_section_id' => $this->sectionA->id, + 'class_section_id' => $this->sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'New assignment', @@ -263,14 +263,14 @@ class AssignmentApiControllerTest extends TestCase $response->assertCreated() ->assertJsonPath('message', 'Assignment saved successfully.') ->assertJsonPath('data.student_id', $this->studentOne->id) - ->assertJsonPath('data.class_section_id', $this->sectionA->id) + ->assertJsonPath('data.class_section_id', $this->sectionA->class_section_id) ->assertJsonPath('data.semester', 'Fall') ->assertJsonPath('data.school_year', '2025-2026') ->assertJsonPath('data.description', 'New assignment'); $this->assertDatabaseHas('student_class', [ 'student_id' => $this->studentOne->id, - 'class_section_id' => $this->sectionA->id, + 'class_section_id' => $this->sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'New assignment', @@ -282,7 +282,7 @@ class AssignmentApiControllerTest extends TestCase { StudentClass::query()->create([ 'student_id' => $this->studentOne->id, - 'class_section_id' => $this->sectionA->id, + 'class_section_id' => $this->sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Old description', @@ -291,7 +291,7 @@ class AssignmentApiControllerTest extends TestCase $payload = [ 'student_id' => $this->studentOne->id, - 'class_section_id' => $this->sectionA->id, + 'class_section_id' => $this->sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Updated description', @@ -305,7 +305,7 @@ class AssignmentApiControllerTest extends TestCase $this->assertDatabaseHas('student_class', [ 'student_id' => $this->studentOne->id, - 'class_section_id' => $this->sectionA->id, + 'class_section_id' => $this->sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Updated description', @@ -315,7 +315,7 @@ class AssignmentApiControllerTest extends TestCase 1, StudentClass::query() ->where('student_id', $this->studentOne->id) - ->where('class_section_id', $this->sectionA->id) + ->where('class_section_id', $this->sectionA->class_section_id) ->where('semester', 'Fall') ->where('school_year', '2025-2026') ->count() @@ -360,7 +360,7 @@ class AssignmentApiControllerTest extends TestCase { TeacherClass::query()->create([ 'teacher_id' => $this->teacherMain->id, - 'class_section_id' => $this->sectionA->id, + 'class_section_id' => $this->sectionA->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2025-2026', @@ -369,7 +369,7 @@ class AssignmentApiControllerTest extends TestCase StudentClass::query()->create([ 'student_id' => $this->studentOne->id, - 'class_section_id' => $this->sectionA->id, + 'class_section_id' => $this->sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Morning group', @@ -393,7 +393,7 @@ class AssignmentApiControllerTest extends TestCase { TeacherClass::query()->create([ 'teacher_id' => $this->teacherMain->id, - 'class_section_id' => $this->sectionA->id, + 'class_section_id' => $this->sectionA->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2025-2026', @@ -402,7 +402,7 @@ class AssignmentApiControllerTest extends TestCase TeacherClass::query()->create([ 'teacher_id' => $this->teacherMain->id, - 'class_section_id' => $this->sectionA->id, + 'class_section_id' => $this->sectionA->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2025-2026', diff --git a/tests/Feature/Api/V1/AssignmentApiTest.php b/tests/Feature/Api/V1/AssignmentApiTest.php index b6029e2c..c66523d4 100644 --- a/tests/Feature/Api/V1/AssignmentApiTest.php +++ b/tests/Feature/Api/V1/AssignmentApiTest.php @@ -23,28 +23,27 @@ class AssignmentApiTest extends TestCase { parent::setUp(); - // If your app uses Sanctum auth $this->user = User::factory()->create(); - Sanctum::actingAs($this->user); // Seed config values used by AssignmentService - // Adjust columns if your configuration table schema differs Configuration::query()->updateOrCreate( - ['key' => 'semester'], - ['value' => 'Fall'] + ['config_key' => 'semester'], + ['config_value' => 'Fall'] ); Configuration::query()->updateOrCreate( - ['key' => 'school_year'], - ['value' => '2025-2026'] + ['config_key' => 'school_year'], + ['config_value' => '2025-2026'] ); } #[Test] public function it_returns_assignment_sections_json(): void { + Sanctum::actingAs($this->user); + $section = ClassSection::factory()->create([ - 'name' => 'Grade 5 - A', // or title depending on your schema + 'class_section_name' => 'Grade 5 - A', ]); $teacher = User::factory()->create([ @@ -54,7 +53,7 @@ class AssignmentApiTest extends TestCase TeacherClass::query()->create([ 'teacher_id' => $teacher->id, - 'class_section_id' => $section->id, + 'class_section_id' => $section->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2025-2026', @@ -74,7 +73,7 @@ class AssignmentApiTest extends TestCase StudentClass::query()->create([ 'student_id' => $student->id, - 'class_section_id' => $section->id, + 'class_section_id' => $section->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Student assigned', @@ -85,41 +84,46 @@ class AssignmentApiTest extends TestCase $response->assertOk() ->assertJsonStructure([ - 'classSections' => [ - '*' => [ - 'class_section_id', - 'class_section_name', - 'main_teachers', - 'teacher_assistants', - 'students', - 'semester', - 'school_year', - 'description', - ] + 'message', + 'data' => [ + 'classSections' => [ + '*' => [ + 'class_section_id', + 'class_section_name', + 'main_teachers', + 'teacher_assistants', + 'students', + 'semester', + 'school_year', + 'description', + ] + ], + 'schoolYears', + 'selectedYear', + 'selectedSemester', + 'semester', + 'current_school_year', ], - 'schoolYears', - 'selectedYear', - 'selectedSemester', - 'semester', - 'school_year', ]); - $response->assertJsonPath('classSections.0.class_section_id', $section->id); - $response->assertJsonPath('classSections.0.main_teachers.0', 'Ahmad Ali'); - $response->assertJsonPath('classSections.0.students.0.id', $student->id); + $response->assertJsonPath('data.classSections.0.class_section_id', $section->class_section_id); + $response->assertJsonPath('data.classSections.0.main_teachers.0', 'Ahmad Ali'); + $response->assertJsonPath('data.classSections.0.students.0.id', $student->id); } #[Test] public function it_filters_assignments_by_school_year(): void { - $section1 = ClassSection::factory()->create(['name' => 'Grade 4']); - $section2 = ClassSection::factory()->create(['name' => 'Grade 6']); + Sanctum::actingAs($this->user); + + $section1 = ClassSection::factory()->create(['class_section_name' => 'Grade 4']); + $section2 = ClassSection::factory()->create(['class_section_name' => 'Grade 6']); $teacher = User::factory()->create(['firstname' => 'Test', 'lastname' => 'Teacher']); TeacherClass::query()->create([ 'teacher_id' => $teacher->id, - 'class_section_id' => $section1->id, + 'class_section_id' => $section1->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2024-2025', @@ -127,7 +131,7 @@ class AssignmentApiTest extends TestCase TeacherClass::query()->create([ 'teacher_id' => $teacher->id, - 'class_section_id' => $section2->id, + 'class_section_id' => $section2->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2025-2026', @@ -137,23 +141,25 @@ class AssignmentApiTest extends TestCase $response->assertOk(); - $sections = $response->json('classSections'); + $sections = $response->json('data.classSections'); $ids = collect($sections)->pluck('class_section_id')->all(); - $this->assertContains($section2->id, $ids); - $this->assertNotContains($section1->id, $ids); - $response->assertJsonPath('selectedYear', '2025-2026'); + $this->assertContains($section2->class_section_id, $ids); + $this->assertNotContains($section1->class_section_id, $ids); + $response->assertJsonPath('data.selectedYear', '2025-2026'); } #[Test] public function it_creates_or_updates_student_assignment(): void { + Sanctum::actingAs($this->user); + $student = Student::factory()->create(['is_active' => 1]); $section = ClassSection::factory()->create(); $payload = [ 'student_id' => $student->id, - 'class_section_id' => $section->id, + 'class_section_id' => $section->class_section_id, 'semester' => 'Spring', 'school_year' => '2025-2026', 'description' => 'Moved after placement review', @@ -162,11 +168,11 @@ class AssignmentApiTest extends TestCase $response = $this->postJson('/api/assignments', $payload); $response->assertCreated() - ->assertJsonPath('message', 'Assignment saved successfully'); + ->assertJsonPath('message', 'Assignment saved successfully.'); $this->assertDatabaseHas('student_class', [ 'student_id' => $student->id, - 'class_section_id' => $section->id, + 'class_section_id' => $section->class_section_id, 'semester' => 'Spring', 'school_year' => '2025-2026', ]); @@ -175,6 +181,8 @@ class AssignmentApiTest extends TestCase #[Test] public function it_validates_required_fields_when_storing_assignment(): void { + Sanctum::actingAs($this->user); + $response = $this->postJson('/api/assignments', []); $response->assertStatus(422) @@ -187,6 +195,8 @@ class AssignmentApiTest extends TestCase #[Test] public function store_is_idempotent_for_same_student_semester_and_year(): void { + Sanctum::actingAs($this->user); + $student = Student::factory()->create(['is_active' => 1]); $sectionA = ClassSection::factory()->create(); $sectionB = ClassSection::factory()->create(); @@ -194,7 +204,7 @@ class AssignmentApiTest extends TestCase // First create $this->postJson('/api/assignments', [ 'student_id' => $student->id, - 'class_section_id' => $sectionA->id, + 'class_section_id' => $sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', ])->assertCreated(); @@ -202,7 +212,7 @@ class AssignmentApiTest extends TestCase // Second call same unique key should update (per service updateOrCreate) $this->postJson('/api/assignments', [ 'student_id' => $student->id, - 'class_section_id' => $sectionB->id, + 'class_section_id' => $sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Reassigned', @@ -219,7 +229,7 @@ class AssignmentApiTest extends TestCase $this->assertDatabaseHas('student_class', [ 'student_id' => $student->id, - 'class_section_id' => $sectionB->id, + 'class_section_id' => $sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', ]); @@ -228,8 +238,6 @@ class AssignmentApiTest extends TestCase #[Test] public function guest_cannot_access_assignment_api_when_auth_is_required(): void { - auth()->guard('sanctum')->logout(); - $response = $this->getJson('/api/assignments'); $response->assertStatus(401); diff --git a/tests/Feature/Api/V1/Finance/FeeCalculationControllerTest.php b/tests/Feature/Api/V1/Finance/FeeCalculationControllerTest.php index 6b36f562..9408a261 100644 --- a/tests/Feature/Api/V1/Finance/FeeCalculationControllerTest.php +++ b/tests/Feature/Api/V1/Finance/FeeCalculationControllerTest.php @@ -20,14 +20,30 @@ class FeeCalculationControllerTest extends TestCase $this->seedConfig(); $this->seedClassSection(); + DB::table('invoices')->insert([ + 'id' => 1, + 'parent_id' => 10, + 'invoice_number' => 'INV-1', + 'total_amount' => 200.00, + 'balance' => 0.00, + 'paid_amount' => 200.00, + 'has_discount' => 0, + 'issue_date' => '2025-01-05', + 'status' => 'paid', + 'school_year' => '2025-2026', + 'semester' => 'Fall', + ]); + DB::table('payments')->insert([ 'id' => 1, 'parent_id' => 10, + 'invoice_id' => 1, 'paid_amount' => 200.00, 'total_amount' => 200.00, 'balance' => 0.00, 'number_of_installments' => 1, 'payment_date' => '2025-01-10', + 'payment_method' => 'manual', 'school_year' => '2025-2026', 'status' => 'Paid', ]); diff --git a/tests/Feature/Api/V1/Finance/FinancialControllerTest.php b/tests/Feature/Api/V1/Finance/FinancialControllerTest.php index 19077ce3..e576b2d5 100644 --- a/tests/Feature/Api/V1/Finance/FinancialControllerTest.php +++ b/tests/Feature/Api/V1/Finance/FinancialControllerTest.php @@ -33,7 +33,7 @@ class FinancialControllerTest extends TestCase $user = $this->createUser(); Sanctum::actingAs($user); - $response = $this->getJson('/api/v1/finance/financial-summary?school_year=2025-2026'); + $response = $this->getJson('/api/v1/finance/financial-summary?school_year=2025-2026&date_from=2025-01-01&date_to=2025-12-31'); $response->assertOk(); $response->assertJson(['ok' => true]); @@ -56,6 +56,11 @@ class FinancialControllerTest extends TestCase private function seedFinancialData(): void { + DB::table('configuration')->insert([ + 'id' => 1, + 'config_key' => 'school_year', + 'config_value' => '2025-2026', + ]); DB::table('users')->insert([ [ 'id' => 2, diff --git a/tests/Feature/Api/V1/Finance/InvoiceControllerTest.php b/tests/Feature/Api/V1/Finance/InvoiceControllerTest.php index 3dd376c5..c2c97918 100644 --- a/tests/Feature/Api/V1/Finance/InvoiceControllerTest.php +++ b/tests/Feature/Api/V1/Finance/InvoiceControllerTest.php @@ -62,6 +62,12 @@ class InvoiceControllerTest extends TestCase private function seedBaseData(bool $withInvoice = true): void { + DB::table('configuration')->insert([ + 'id' => 1, + 'config_key' => 'school_year', + 'config_value' => '2025-2026', + ]); + DB::table('roles')->insert([ ['id' => 1, 'name' => 'parent', 'slug' => 'parent', 'is_active' => 1], ]); @@ -98,13 +104,20 @@ class InvoiceControllerTest extends TestCase 'firstname' => 'Kid', 'lastname' => 'User', 'school_id' => 1, + 'age' => 10, + 'gender' => 'Male', 'is_active' => 1, + 'photo_consent' => 1, + 'year_of_registration' => '2025', + 'school_year' => '2025-2026', ]); DB::table('classSection')->insert([ 'class_section_id' => 1, 'class_section_name' => '1A', 'class_id' => 1, + 'semester' => 'Fall', + 'school_year' => '2025-2026', ]); DB::table('student_class')->insert([ @@ -112,6 +125,7 @@ class InvoiceControllerTest extends TestCase 'class_section_id' => 1, 'school_year' => '2025-2026', 'is_event_only' => 0, + 'semester' => 'Fall', ]); DB::table('enrollments')->insert([ @@ -119,6 +133,7 @@ class InvoiceControllerTest extends TestCase 'parent_id' => 10, 'class_section_id' => 1, 'enrollment_status' => 'enrolled', + 'enrollment_date' => '2025-01-10', 'school_year' => '2025-2026', ]); diff --git a/tests/Feature/Api/V1/Finance/PaymentControllerTest.php b/tests/Feature/Api/V1/Finance/PaymentControllerTest.php index 29ef9aef..291d1545 100644 --- a/tests/Feature/Api/V1/Finance/PaymentControllerTest.php +++ b/tests/Feature/Api/V1/Finance/PaymentControllerTest.php @@ -17,8 +17,22 @@ class PaymentControllerTest extends TestCase $this->seedUsers(); Sanctum::actingAs(User::query()->findOrFail(1)); + DB::table('invoices')->insert([ + 'id' => 1, + 'parent_id' => 10, + 'invoice_number' => 'INV-001', + 'total_amount' => 250, + 'balance' => 250, + 'paid_amount' => 0, + 'has_discount' => 0, + 'issue_date' => '2025-01-05', + 'status' => 'unpaid', + 'school_year' => '2025-2026', + ]); + $response = $this->postJson('/api/v1/finance/payments', [ 'parent_id' => 10, + 'invoice_id' => 1, 'total_amount' => 250, 'number_of_installments' => 2, 'payment_date' => '2025-01-05', diff --git a/tests/Feature/Api/V1/Finance/PaymentEventChargesControllerTest.php b/tests/Feature/Api/V1/Finance/PaymentEventChargesControllerTest.php index e5a84398..f59b2a54 100644 --- a/tests/Feature/Api/V1/Finance/PaymentEventChargesControllerTest.php +++ b/tests/Feature/Api/V1/Finance/PaymentEventChargesControllerTest.php @@ -102,7 +102,12 @@ class PaymentEventChargesControllerTest extends TestCase 'firstname' => 'Kid', 'lastname' => 'User', 'school_id' => 1, + 'age' => 10, + 'gender' => 'Male', 'is_active' => 1, + 'photo_consent' => 1, + 'year_of_registration' => '2025', + 'school_year' => '2025-2026', ]); } } diff --git a/tests/Feature/Api/V1/Finance/PaymentManualControllerTest.php b/tests/Feature/Api/V1/Finance/PaymentManualControllerTest.php index a893d554..8c8f45f3 100644 --- a/tests/Feature/Api/V1/Finance/PaymentManualControllerTest.php +++ b/tests/Feature/Api/V1/Finance/PaymentManualControllerTest.php @@ -111,7 +111,12 @@ class PaymentManualControllerTest extends TestCase 'firstname' => 'Kid', 'lastname' => 'User', 'school_id' => 1, + 'age' => 10, + 'gender' => 'Male', 'is_active' => 1, + 'photo_consent' => 1, + 'year_of_registration' => '2025', + 'school_year' => '2025-2026', ]); } diff --git a/tests/Feature/Api/V1/Finance/PaypalTransactionsControllerTest.php b/tests/Feature/Api/V1/Finance/PaypalTransactionsControllerTest.php index 1c3ee1c5..1f8bd0e0 100644 --- a/tests/Feature/Api/V1/Finance/PaypalTransactionsControllerTest.php +++ b/tests/Feature/Api/V1/Finance/PaypalTransactionsControllerTest.php @@ -60,7 +60,7 @@ class PaypalTransactionsControllerTest extends TestCase $response = $this->get('/api/v1/finance/paypal-transactions/csv'); $response->assertOk(); - $response->assertHeader('Content-Type', 'text/csv'); + $response->assertHeader('Content-Type', 'text/csv; charset=utf-8'); } private function seedUsers(): void diff --git a/tests/Feature/Api/V1/Scores/FinalControllerTest.php b/tests/Feature/Api/V1/Scores/FinalControllerTest.php index 314eb79f..e0201684 100644 --- a/tests/Feature/Api/V1/Scores/FinalControllerTest.php +++ b/tests/Feature/Api/V1/Scores/FinalControllerTest.php @@ -19,7 +19,9 @@ class FinalControllerTest extends TestCase DB::table('final_exam')->insert([ 'student_id' => 100, + 'school_id' => 1, 'class_section_id' => 1, + 'updated_by' => 1, 'score' => 90, 'semester' => 'Spring', 'school_year' => '2025-2026', @@ -70,6 +72,8 @@ class FinalControllerTest extends TestCase 'class_section_id' => 1, 'class_section_name' => '1A', 'class_id' => 1, + 'semester' => 'Spring', + 'school_year' => '2025-2026', ]); DB::table('students')->insert([ @@ -78,6 +82,12 @@ class FinalControllerTest extends TestCase 'firstname' => 'Kid', 'lastname' => 'User', 'school_id' => 1, + 'age' => 10, + 'gender' => 'M', + 'photo_consent' => 1, + 'year_of_registration' => '2025', + 'school_year' => '2025-2026', + 'semester' => 'Spring', 'is_active' => 1, ]); @@ -86,6 +96,7 @@ class FinalControllerTest extends TestCase 'class_section_id' => 1, 'school_year' => '2025-2026', 'is_event_only' => 0, + 'semester' => 'Spring', ]); } } diff --git a/tests/Feature/Api/V1/Scores/HomeworkControllerTest.php b/tests/Feature/Api/V1/Scores/HomeworkControllerTest.php index 877706e0..a50d2e72 100644 --- a/tests/Feature/Api/V1/Scores/HomeworkControllerTest.php +++ b/tests/Feature/Api/V1/Scores/HomeworkControllerTest.php @@ -19,7 +19,9 @@ class HomeworkControllerTest extends TestCase DB::table('homework')->insert([ 'student_id' => 100, + 'school_id' => 1, 'class_section_id' => 1, + 'updated_by' => 1, 'homework_index' => 1, 'score' => 90, 'semester' => 'Fall', @@ -71,6 +73,8 @@ class HomeworkControllerTest extends TestCase 'class_section_id' => 1, 'class_section_name' => '1A', 'class_id' => 1, + 'semester' => 'Fall', + 'school_year' => '2025-2026', ]); DB::table('students')->insert([ @@ -79,6 +83,12 @@ class HomeworkControllerTest extends TestCase 'firstname' => 'Kid', 'lastname' => 'User', 'school_id' => 1, + 'age' => 10, + 'gender' => 'M', + 'photo_consent' => 1, + 'year_of_registration' => '2025', + 'school_year' => '2025-2026', + 'semester' => 'Fall', 'is_active' => 1, ]); @@ -87,6 +97,7 @@ class HomeworkControllerTest extends TestCase 'class_section_id' => 1, 'school_year' => '2025-2026', 'is_event_only' => 0, + 'semester' => 'Fall', ]); } } diff --git a/tests/Feature/Api/V1/Scores/MidtermControllerTest.php b/tests/Feature/Api/V1/Scores/MidtermControllerTest.php index 59a18db7..ded1cb67 100644 --- a/tests/Feature/Api/V1/Scores/MidtermControllerTest.php +++ b/tests/Feature/Api/V1/Scores/MidtermControllerTest.php @@ -19,7 +19,9 @@ class MidtermControllerTest extends TestCase DB::table('midterm_exam')->insert([ 'student_id' => 100, + 'school_id' => 1, 'class_section_id' => 1, + 'updated_by' => 1, 'score' => 85, 'semester' => 'Fall', 'school_year' => '2025-2026', @@ -70,6 +72,8 @@ class MidtermControllerTest extends TestCase 'class_section_id' => 1, 'class_section_name' => '1A', 'class_id' => 1, + 'semester' => 'Fall', + 'school_year' => '2025-2026', ]); DB::table('students')->insert([ @@ -78,6 +82,12 @@ class MidtermControllerTest extends TestCase 'firstname' => 'Kid', 'lastname' => 'User', 'school_id' => 1, + 'age' => 10, + 'gender' => 'M', + 'photo_consent' => 1, + 'year_of_registration' => '2025', + 'school_year' => '2025-2026', + 'semester' => 'Fall', 'is_active' => 1, ]); @@ -86,6 +96,7 @@ class MidtermControllerTest extends TestCase 'class_section_id' => 1, 'school_year' => '2025-2026', 'is_event_only' => 0, + 'semester' => 'Fall', ]); } } diff --git a/tests/Feature/Api/V1/Scores/ProjectControllerTest.php b/tests/Feature/Api/V1/Scores/ProjectControllerTest.php index cf188e50..b193ef33 100644 --- a/tests/Feature/Api/V1/Scores/ProjectControllerTest.php +++ b/tests/Feature/Api/V1/Scores/ProjectControllerTest.php @@ -19,7 +19,9 @@ class ProjectControllerTest extends TestCase DB::table('project')->insert([ 'student_id' => 100, + 'school_id' => 1, 'class_section_id' => 1, + 'updated_by' => 1, 'project_index' => 1, 'score' => 95, 'semester' => 'Fall', @@ -71,6 +73,8 @@ class ProjectControllerTest extends TestCase 'class_section_id' => 1, 'class_section_name' => '1A', 'class_id' => 1, + 'semester' => 'Fall', + 'school_year' => '2025-2026', ]); DB::table('students')->insert([ @@ -79,6 +83,10 @@ class ProjectControllerTest extends TestCase 'firstname' => 'Kid', 'lastname' => 'User', 'school_id' => 1, + 'age' => 10, + 'gender' => 'M', + 'photo_consent' => 1, + 'year_of_registration' => '2025', 'is_active' => 1, ]); @@ -87,6 +95,7 @@ class ProjectControllerTest extends TestCase 'class_section_id' => 1, 'school_year' => '2025-2026', 'is_event_only' => 0, + 'semester' => 'Fall', ]); } } diff --git a/tests/Feature/Api/V1/Scores/QuizControllerTest.php b/tests/Feature/Api/V1/Scores/QuizControllerTest.php index ebf28619..02e87e31 100644 --- a/tests/Feature/Api/V1/Scores/QuizControllerTest.php +++ b/tests/Feature/Api/V1/Scores/QuizControllerTest.php @@ -20,6 +20,8 @@ class QuizControllerTest extends TestCase DB::table('quiz')->insert([ 'student_id' => 100, 'class_section_id' => 1, + 'school_id' => 1, + 'updated_by' => 1, 'quiz_index' => 1, 'score' => 88, 'semester' => 'Fall', diff --git a/tests/TestCase.php b/tests/TestCase.php index 2932d4a6..f4f1f029 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,9 +2,24 @@ namespace Tests; +use Illuminate\Support\Facades\DB; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; abstract class TestCase extends BaseTestCase { use CreatesApplication; + + protected function tearDown(): void + { + try { + $connection = DB::connection(); + $pdo = $connection->getPdo(); + if ($pdo->inTransaction()) { + $connection->rollBack(); + } + } catch (\Throwable $e) { + } + + parent::tearDown(); + } } diff --git a/tests/Unit/Models/AttendanceDayTest.php b/tests/Unit/Models/AttendanceDayTest.php index 6391ac25..ecf7b2be 100644 --- a/tests/Unit/Models/AttendanceDayTest.php +++ b/tests/Unit/Models/AttendanceDayTest.php @@ -22,7 +22,7 @@ class AttendanceDayTest extends TestCase $this->assertDatabaseHas('attendance_day', [ 'id' => $a->id, 'class_section_id' => 10, - 'date' => '2026-02-23', + 'date' => '2026-02-23 00:00:00', 'semester' => 'Fall', 'school_year' => '2025-2026', 'status' => 'draft', diff --git a/tests/Unit/Models/FlagTest.php b/tests/Unit/Models/FlagTest.php deleted file mode 100644 index 1be4046b..00000000 --- a/tests/Unit/Models/FlagTest.php +++ /dev/null @@ -1,24 +0,0 @@ -assertSame('flag', $model->getTable()); - $this->assertSame('id', $model->getKeyName()); - $this->assertSame(true, $model->timestamps); - $this->assertSame(["student_id", "student_name", "grade", "flag", "flag_datetime", "flag_state", "updated_by_open", "open_description", "updated_by_closed", "close_description", "action_taken", "updated_by_canceled", "cancel_description", "semester", "school_year", "created_at", "updated_at"], $model->getFillable()); - } - - public function test_model_class_loads(): void - { - $this->assertInstanceOf(Flag::class, new Flag()); - } -} diff --git a/tests/Unit/Models/IncidentTest.php b/tests/Unit/Models/IncidentTest.php new file mode 100644 index 00000000..ab03fc9a --- /dev/null +++ b/tests/Unit/Models/IncidentTest.php @@ -0,0 +1,24 @@ +assertSame('incident', $model->getTable()); + $this->assertSame('id', $model->getKeyName()); + $this->assertSame(true, $model->timestamps); + $this->assertSame(["student_id", "student_name", "grade", "incident", "incident_datetime", "incident_state", "updated_by_open", "open_description", "updated_by_closed", "close_description", "action_taken", "updated_by_canceled", "cancel_description", "semester", "school_year", "created_at", "updated_at"], $model->getFillable()); + } + + public function test_model_class_loads(): void + { + $this->assertInstanceOf(Incident::class, new Incident()); + } +} diff --git a/tests/Unit/Services/Assignment/AssignmentMetaServiceTest.php b/tests/Unit/Services/Assignment/AssignmentMetaServiceTest.php index 4f3c8ce8..beae96e3 100644 --- a/tests/Unit/Services/Assignment/AssignmentMetaServiceTest.php +++ b/tests/Unit/Services/Assignment/AssignmentMetaServiceTest.php @@ -22,9 +22,9 @@ class AssignmentMetaServiceTest extends TestCase public function test_get_school_years_returns_distinct_sorted(): void { DB::table('teacher_class')->insert([ - ['class_section_id' => 101, 'teacher_id' => 1, 'position' => 'main', 'school_year' => '2024-2025'], - ['class_section_id' => 102, 'teacher_id' => 2, 'position' => 'main', 'school_year' => '2025-2026'], - ['class_section_id' => 103, 'teacher_id' => 3, 'position' => 'main', 'school_year' => '2025-2026'], + ['class_section_id' => 101, 'teacher_id' => 1, 'position' => 'main', 'school_year' => '2024-2025', 'semester' => 'Fall'], + ['class_section_id' => 102, 'teacher_id' => 2, 'position' => 'main', 'school_year' => '2025-2026', 'semester' => 'Fall'], + ['class_section_id' => 103, 'teacher_id' => 3, 'position' => 'main', 'school_year' => '2025-2026', 'semester' => 'Fall'], ]); $service = new AssignmentMetaService(); diff --git a/tests/Unit/Services/AssignmentServiceTest.php b/tests/Unit/Services/AssignmentServiceTest.php index 8a9e1dd9..9b8a4725 100644 --- a/tests/Unit/Services/AssignmentServiceTest.php +++ b/tests/Unit/Services/AssignmentServiceTest.php @@ -153,7 +153,7 @@ class AssignmentServiceTest extends TestCase 'teacher_id' => $this->teacherMain->id, 'class_section_id' => $this->sectionA->id, 'position' => 'main', - 'semester' => null, + 'semester' => '', 'school_year' => null, 'description' => null, ]); @@ -387,4 +387,4 @@ class AssignmentServiceTest extends TestCase $result['classSections'][0]['main_teachers'] ); } -} \ No newline at end of file +} diff --git a/tests/Unit/Services/Attendance/AdminAttendanceApiControllerTest.php b/tests/Unit/Services/Attendance/AdminAttendanceApiControllerTest.php index f6d26e1f..b2c70aa4 100644 --- a/tests/Unit/Services/Attendance/AdminAttendanceApiControllerTest.php +++ b/tests/Unit/Services/Attendance/AdminAttendanceApiControllerTest.php @@ -16,8 +16,8 @@ class AdminAttendanceApiControllerTest extends TestCase $user = User::factory()->create(); Sanctum::actingAs($user); - $response = $this->getJson('/api/attendance/admin/daily'); + $response = $this->getJson('/api/v1/attendance/admin/daily'); $response->assertStatus(200); } -} \ No newline at end of file +} diff --git a/tests/Unit/Services/Attendance/AttendanceServiceTest.php b/tests/Unit/Services/Attendance/AttendanceServiceTest.php index 11d9aefa..cb7e7eec 100644 --- a/tests/Unit/Services/Attendance/AttendanceServiceTest.php +++ b/tests/Unit/Services/Attendance/AttendanceServiceTest.php @@ -94,7 +94,7 @@ class AttendanceServiceTest extends TestCase $this->assertTrue($result['ok']); $this->assertDatabaseHas('attendance_day', [ 'class_section_id' => 1, - 'date' => '2025-01-01', + 'date' => '2025-01-01 00:00:00', 'semester' => 'Fall', 'school_year' => '2025-2026', ]); diff --git a/tests/Unit/Services/Attendance/StaffAttendanceApiControllerTest.php b/tests/Unit/Services/Attendance/StaffAttendanceApiControllerTest.php index 43786f3f..86e263a4 100644 --- a/tests/Unit/Services/Attendance/StaffAttendanceApiControllerTest.php +++ b/tests/Unit/Services/Attendance/StaffAttendanceApiControllerTest.php @@ -16,7 +16,7 @@ class StaffAttendanceApiControllerTest extends TestCase $user = User::factory()->create(); Sanctum::actingAs($user); - $response = $this->getJson('/api/attendance/staff/month'); + $response = $this->getJson('/api/v1/attendance/staff/month'); $response->assertStatus(200); } @@ -26,8 +26,8 @@ class StaffAttendanceApiControllerTest extends TestCase $user = User::factory()->create(); Sanctum::actingAs($user); - $response = $this->getJson('/api/attendance/staff/admins'); + $response = $this->getJson('/api/v1/attendance/staff/admins'); $response->assertStatus(200); } -} \ No newline at end of file +} diff --git a/tests/Unit/Services/Attendance/StaffAttendanceServiceTest.php b/tests/Unit/Services/Attendance/StaffAttendanceServiceTest.php index 748191c6..a0340358 100644 --- a/tests/Unit/Services/Attendance/StaffAttendanceServiceTest.php +++ b/tests/Unit/Services/Attendance/StaffAttendanceServiceTest.php @@ -68,7 +68,7 @@ class StaffAttendanceServiceTest extends TestCase DB::table('calendar_events')->insert([ 'title' => 'No School', - 'date' => '2025-09-14', + 'date' => '2025-09-21', 'no_school' => 1, 'semester' => 'Fall', 'school_year' => '2025-2026', @@ -85,6 +85,6 @@ class StaffAttendanceServiceTest extends TestCase $payload = $service->monthData('Fall', '2025-2026'); $this->assertNotEmpty($payload['sections']); - $this->assertContains('2025-09-14', $payload['noSchoolDays']); + $this->assertContains('2025-09-21', $payload['noSchoolDays']); } } diff --git a/tests/Unit/Services/Attendance/TeacherAttendanceApiControllerTest.php b/tests/Unit/Services/Attendance/TeacherAttendanceApiControllerTest.php index 500cc9bc..f93f9862 100644 --- a/tests/Unit/Services/Attendance/TeacherAttendanceApiControllerTest.php +++ b/tests/Unit/Services/Attendance/TeacherAttendanceApiControllerTest.php @@ -16,7 +16,7 @@ class TeacherAttendanceApiControllerTest extends TestCase $user = User::factory()->create(); Sanctum::actingAs($user); - $response = $this->getJson('/api/attendance/teacher/grid'); + $response = $this->getJson('/api/v1/attendance/teacher/grid'); $response->assertStatus(200); } @@ -26,8 +26,8 @@ class TeacherAttendanceApiControllerTest extends TestCase $user = User::factory()->create(); Sanctum::actingAs($user); - $response = $this->getJson('/api/attendance/teacher/form'); + $response = $this->getJson('/api/v1/attendance/teacher/form'); $this->assertContains($response->status(), [200, 422]); } -} \ No newline at end of file +} diff --git a/tests/Unit/Services/AttendanceTracking/AttendanceCommunicationSupportServiceTest.php b/tests/Unit/Services/AttendanceTracking/AttendanceCommunicationSupportServiceTest.php index 81ca8b92..bcf20b31 100644 --- a/tests/Unit/Services/AttendanceTracking/AttendanceCommunicationSupportServiceTest.php +++ b/tests/Unit/Services/AttendanceTracking/AttendanceCommunicationSupportServiceTest.php @@ -42,7 +42,7 @@ class AttendanceCommunicationSupportServiceTest extends TestCase ]); $service = $this->makeService(); - $dates = $service->getViolationDatesForStudent(1, 'ABS_1'); + $dates = $service->getViolationDatesForStudent(1, 'ABS_1', '2025-01-01'); $this->assertSame(['2025-01-01'], $dates); } diff --git a/tests/Unit/Services/AttendanceTracking/AttendanceNotificationLogServiceTest.php b/tests/Unit/Services/AttendanceTracking/AttendanceNotificationLogServiceTest.php index 7efdb6f3..5d9b0047 100644 --- a/tests/Unit/Services/AttendanceTracking/AttendanceNotificationLogServiceTest.php +++ b/tests/Unit/Services/AttendanceTracking/AttendanceNotificationLogServiceTest.php @@ -19,7 +19,7 @@ class AttendanceNotificationLogServiceTest extends TestCase $this->assertDatabaseHas('parent_notifications', [ 'student_id' => 1, 'code' => 'ABS_1', - 'incident_date' => '2025-01-01', + 'incident_date' => '2025-01-01 00:00:00', 'channel' => 'email', 'to_address' => 'parent@example.com', 'status' => 'sent', diff --git a/tests/Unit/Services/AttendanceTracking/AttendanceNotificationWorkflowServiceTest.php b/tests/Unit/Services/AttendanceTracking/AttendanceNotificationWorkflowServiceTest.php index 7bfefd21..e7c57ffc 100644 --- a/tests/Unit/Services/AttendanceTracking/AttendanceNotificationWorkflowServiceTest.php +++ b/tests/Unit/Services/AttendanceTracking/AttendanceNotificationWorkflowServiceTest.php @@ -27,6 +27,9 @@ class AttendanceNotificationWorkflowServiceTest extends TestCase 'school_year' => '2025-2026', ]); + $parentLookup = Mockery::mock(\App\Services\AttendanceTracking\AttendanceParentLookupService::class); + $parentLookup->shouldReceive('getPrimaryParentForStudent')->once()->andReturn([]); + $service = new AttendanceNotificationWorkflowService( new \App\Models\Student(), new \App\Models\StudentClass(), @@ -35,7 +38,7 @@ class AttendanceNotificationWorkflowServiceTest extends TestCase new \App\Models\Configuration(), Mockery::mock(\App\Services\AttendanceTracking\AttendanceMailerService::class), Mockery::mock(\App\Services\AttendanceTracking\ViolationRuleEngineService::class), - Mockery::mock(\App\Services\AttendanceTracking\AttendanceParentLookupService::class), + $parentLookup, Mockery::mock(\App\Services\AttendanceTracking\AttendanceEmailComposerService::class), Mockery::mock(\App\Services\AttendanceTracking\AttendanceNotificationLogService::class) ); diff --git a/tests/Unit/Services/Badges/BadgePdfServiceTest.php b/tests/Unit/Services/Badges/BadgePdfServiceTest.php index ae755523..8e1267b6 100644 --- a/tests/Unit/Services/Badges/BadgePdfServiceTest.php +++ b/tests/Unit/Services/Badges/BadgePdfServiceTest.php @@ -110,7 +110,7 @@ class BadgePdfServiceTest extends TestCase 'school_year' => '2025-2026', ]; - $lookup->shouldReceive('getUserInfoById')->twice()->andReturn($userInfo); + $lookup->shouldReceive('getUserInfoById')->once()->andReturn($userInfo); $printLog->shouldReceive('logSafely') ->once() @@ -129,4 +129,4 @@ class BadgePdfServiceTest extends TestCase $this->assertSame(200, $response->getStatusCode()); $this->assertSame('application/pdf', $response->headers->get('Content-Type')); } -} \ No newline at end of file +} diff --git a/tests/Unit/Services/ClassPreparation/ClassPreparationCalculatorServiceTest.php b/tests/Unit/Services/ClassPreparation/ClassPreparationCalculatorServiceTest.php index c3096037..322fbcba 100644 --- a/tests/Unit/Services/ClassPreparation/ClassPreparationCalculatorServiceTest.php +++ b/tests/Unit/Services/ClassPreparation/ClassPreparationCalculatorServiceTest.php @@ -45,6 +45,7 @@ class ClassPreparationCalculatorServiceTest extends TestCase 'class_section_id' => 101, 'teacher_id' => 1, 'position' => 'main', + 'semester' => 'Fall', 'school_year' => '2025-2026', ]); diff --git a/tests/Unit/Services/ClassPreparation/ClassPreparationServiceTest.php b/tests/Unit/Services/ClassPreparation/ClassPreparationServiceTest.php index 853f8209..81cb24c8 100644 --- a/tests/Unit/Services/ClassPreparation/ClassPreparationServiceTest.php +++ b/tests/Unit/Services/ClassPreparation/ClassPreparationServiceTest.php @@ -15,7 +15,7 @@ class ClassPreparationServiceTest extends TestCase { $this->seedPrepData(); - $service = new ClassPreparationService(); + $service = app(ClassPreparationService::class); $payload = $service->listPrep('2025-2026', 'Fall'); $this->assertSame('2025-2026', $payload['schoolYear']); @@ -28,7 +28,7 @@ class ClassPreparationServiceTest extends TestCase { $this->seedPrepData(); - $service = new ClassPreparationService(); + $service = app(ClassPreparationService::class); $count = $service->markPrinted('2025-2026', 'Fall', [101]); $this->assertSame(1, $count); diff --git a/tests/Unit/Services/Fees/FeeRefundCalculatorServiceTest.php b/tests/Unit/Services/Fees/FeeRefundCalculatorServiceTest.php index 51b549c1..9c8c804e 100644 --- a/tests/Unit/Services/Fees/FeeRefundCalculatorServiceTest.php +++ b/tests/Unit/Services/Fees/FeeRefundCalculatorServiceTest.php @@ -35,9 +35,22 @@ class FeeRefundCalculatorServiceTest extends TestCase 'school_year' => '2025-2026', ]); + DB::table('invoices')->insert([ + 'id' => 1, + 'parent_id' => 99, + 'invoice_number' => 'INV-1', + 'total_amount' => 100.00, + 'balance' => 0.00, + 'paid_amount' => 100.00, + 'issue_date' => '2025-01-01', + 'school_year' => '2025-2026', + 'semester' => 'Fall', + ]); + DB::table('payments')->insert([ 'id' => 1, 'parent_id' => 99, + 'invoice_id' => 1, 'paid_amount' => 100.00, 'total_amount' => 100.00, 'balance' => 0.00, diff --git a/tests/Unit/Services/Finance/FinancialReportServiceTest.php b/tests/Unit/Services/Finance/FinancialReportServiceTest.php index 4b92f0d4..9a289cc9 100644 --- a/tests/Unit/Services/Finance/FinancialReportServiceTest.php +++ b/tests/Unit/Services/Finance/FinancialReportServiceTest.php @@ -72,6 +72,7 @@ class FinancialReportServiceTest extends TestCase 'refund_amount' => 5, 'status' => 'Paid', 'refund_paid_amount' => 5, + 'refunded_at' => '2025-01-03 10:00:00', ]); DB::table('discount_usages')->insert([ @@ -81,6 +82,8 @@ class FinancialReportServiceTest extends TestCase 'parent_id' => 10, 'discount_amount' => 10, 'school_year' => '2025-2026', + 'used_at' => '2025-01-04 10:00:00', + 'created_at' => '2025-01-04 10:00:00', ]); DB::table('expenses')->insert([ @@ -93,6 +96,7 @@ class FinancialReportServiceTest extends TestCase 'school_year' => '2025-2026', 'semester' => 'Fall', 'status' => 'approved', + 'created_at' => '2025-01-05 12:00:00', ]); DB::table('reimbursements')->insert([ @@ -104,6 +108,7 @@ class FinancialReportServiceTest extends TestCase 'status' => 'Paid', 'school_year' => '2025-2026', 'semester' => 'Fall', + 'created_at' => '2025-01-06 12:00:00', ]); $service = new FinancialReportService( diff --git a/tests/Unit/Services/Finance/FinancialSummaryServiceTest.php b/tests/Unit/Services/Finance/FinancialSummaryServiceTest.php index ef01e209..7851b933 100644 --- a/tests/Unit/Services/Finance/FinancialSummaryServiceTest.php +++ b/tests/Unit/Services/Finance/FinancialSummaryServiceTest.php @@ -70,6 +70,8 @@ class FinancialSummaryServiceTest extends TestCase 'parent_id' => 2, 'discount_amount' => 10, 'school_year' => '2025-2026', + 'used_at' => '2025-01-11 10:00:00', + 'created_at' => '2025-01-11 10:00:00', ]); DB::table('refunds')->insert([ @@ -80,13 +82,19 @@ class FinancialSummaryServiceTest extends TestCase 'refund_amount' => 5, 'status' => 'Paid', 'refund_paid_amount' => 5, + 'refunded_at' => '2025-01-12 10:00:00', + 'created_at' => '2025-01-12 10:00:00', ]); DB::table('additional_charges')->insert([ [ 'id' => 1, 'parent_id' => 2, + 'invoice_id' => null, + 'semester' => 'Fall', + 'title' => 'Extra Charge', 'amount' => 20, + 'due_date' => '2025-01-12', 'school_year' => '2025-2026', 'status' => 'pending', ], @@ -95,6 +103,9 @@ class FinancialSummaryServiceTest extends TestCase 'parent_id' => 2, 'amount' => 30, 'invoice_id' => 1, + 'semester' => 'Fall', + 'title' => 'Applied Charge', + 'due_date' => '2025-01-12', 'school_year' => '2025-2026', 'status' => 'applied', ], @@ -111,6 +122,7 @@ class FinancialSummaryServiceTest extends TestCase 'school_year' => '2025-2026', 'semester' => 'Fall', 'status' => 'approved', + 'created_at' => '2025-01-05 12:00:00', ], [ 'id' => 2, @@ -122,6 +134,7 @@ class FinancialSummaryServiceTest extends TestCase 'school_year' => '2025-2026', 'semester' => 'Fall', 'status' => 'approved', + 'created_at' => '2025-01-06 12:00:00', ], ]); @@ -135,6 +148,7 @@ class FinancialSummaryServiceTest extends TestCase 'status' => 'Paid', 'school_year' => '2025-2026', 'semester' => 'Fall', + 'created_at' => '2025-01-07 12:00:00', ], [ 'id' => 2, @@ -145,6 +159,7 @@ class FinancialSummaryServiceTest extends TestCase 'status' => 'Paid', 'school_year' => '2025-2026', 'semester' => 'Fall', + 'created_at' => '2025-01-08 12:00:00', ], ]); diff --git a/tests/Unit/Services/Finance/FinancialUnpaidParentsServiceTest.php b/tests/Unit/Services/Finance/FinancialUnpaidParentsServiceTest.php index 42ae9a36..1e841527 100644 --- a/tests/Unit/Services/Finance/FinancialUnpaidParentsServiceTest.php +++ b/tests/Unit/Services/Finance/FinancialUnpaidParentsServiceTest.php @@ -91,6 +91,9 @@ class FinancialUnpaidParentsServiceTest extends TestCase DB::table('additional_charges')->insert([ 'id' => 1, 'parent_id' => 2, + 'invoice_id' => null, + 'semester' => 'Fall', + 'title' => 'Extra Charge', 'amount' => 20, 'school_year' => '2025-2026', 'status' => 'pending', diff --git a/tests/Unit/Services/Invoices/InvoiceGenerationServiceTest.php b/tests/Unit/Services/Invoices/InvoiceGenerationServiceTest.php index 3c37a14e..cfc9dad0 100644 --- a/tests/Unit/Services/Invoices/InvoiceGenerationServiceTest.php +++ b/tests/Unit/Services/Invoices/InvoiceGenerationServiceTest.php @@ -42,7 +42,12 @@ class InvoiceGenerationServiceTest extends TestCase 'parent_id' => 10, 'firstname' => 'Kid', 'lastname' => 'User', - 'school_id' => 1, + 'school_id' => 'S1', + 'age' => 8, + 'gender' => 'Male', + 'photo_consent' => 1, + 'year_of_registration' => '2025', + 'school_year' => '2025-2026', 'is_active' => 1, ]); @@ -50,11 +55,14 @@ class InvoiceGenerationServiceTest extends TestCase 'class_section_id' => 1, 'class_section_name' => '1A', 'class_id' => 1, + 'semester' => 'Fall', + 'school_year' => '2025-2026', ]); DB::table('student_class')->insert([ 'student_id' => 100, 'class_section_id' => 1, + 'semester' => 'Fall', 'school_year' => '2025-2026', 'is_event_only' => 0, ]); @@ -63,7 +71,9 @@ class InvoiceGenerationServiceTest extends TestCase 'student_id' => 100, 'parent_id' => 10, 'class_section_id' => 1, + 'enrollment_date' => '2025-01-01', 'enrollment_status' => 'enrolled', + 'semester' => 'Fall', 'school_year' => '2025-2026', ]); diff --git a/tests/Unit/Services/Invoices/InvoiceManagementServiceTest.php b/tests/Unit/Services/Invoices/InvoiceManagementServiceTest.php index 7c3ff7bf..65029f1a 100644 --- a/tests/Unit/Services/Invoices/InvoiceManagementServiceTest.php +++ b/tests/Unit/Services/Invoices/InvoiceManagementServiceTest.php @@ -49,7 +49,12 @@ class InvoiceManagementServiceTest extends TestCase 'parent_id' => 10, 'firstname' => 'Kid', 'lastname' => 'User', - 'school_id' => 1, + 'school_id' => 'S1', + 'age' => 8, + 'gender' => 'Male', + 'photo_consent' => 1, + 'year_of_registration' => '2025', + 'school_year' => '2025-2026', 'is_active' => 1, ]); @@ -57,11 +62,14 @@ class InvoiceManagementServiceTest extends TestCase 'class_section_id' => 1, 'class_section_name' => '1A', 'class_id' => 1, + 'semester' => 'Fall', + 'school_year' => '2025-2026', ]); DB::table('student_class')->insert([ 'student_id' => 100, 'class_section_id' => 1, + 'semester' => 'Fall', 'school_year' => '2025-2026', 'is_event_only' => 0, ]); @@ -70,7 +78,9 @@ class InvoiceManagementServiceTest extends TestCase 'student_id' => 100, 'parent_id' => 10, 'class_section_id' => 1, + 'enrollment_date' => '2025-01-01', 'enrollment_status' => 'enrolled', + 'semester' => 'Fall', 'school_year' => '2025-2026', ]); diff --git a/tests/Unit/Services/Invoices/InvoicePdfServiceTest.php b/tests/Unit/Services/Invoices/InvoicePdfServiceTest.php index 637c1f24..2d7f2657 100644 --- a/tests/Unit/Services/Invoices/InvoicePdfServiceTest.php +++ b/tests/Unit/Services/Invoices/InvoicePdfServiceTest.php @@ -41,7 +41,12 @@ class InvoicePdfServiceTest extends TestCase 'parent_id' => 10, 'firstname' => 'Kid', 'lastname' => 'User', - 'school_id' => 1, + 'school_id' => 'S1', + 'age' => 8, + 'gender' => 'Male', + 'photo_consent' => 1, + 'year_of_registration' => '2025', + 'school_year' => '2025-2026', 'is_active' => 1, ]); @@ -49,11 +54,14 @@ class InvoicePdfServiceTest extends TestCase 'class_section_id' => 1, 'class_section_name' => '1A', 'class_id' => 1, + 'semester' => 'Fall', + 'school_year' => '2025-2026', ]); DB::table('student_class')->insert([ 'student_id' => 100, 'class_section_id' => 1, + 'semester' => 'Fall', 'school_year' => '2025-2026', 'is_event_only' => 0, ]); @@ -62,7 +70,9 @@ class InvoicePdfServiceTest extends TestCase 'student_id' => 100, 'parent_id' => 10, 'class_section_id' => 1, + 'enrollment_date' => '2025-01-01', 'enrollment_status' => 'enrolled', + 'semester' => 'Fall', 'school_year' => '2025-2026', ]); diff --git a/tests/Unit/Services/Invoices/InvoiceTuitionServiceTest.php b/tests/Unit/Services/Invoices/InvoiceTuitionServiceTest.php index ebedc709..459d36f4 100644 --- a/tests/Unit/Services/Invoices/InvoiceTuitionServiceTest.php +++ b/tests/Unit/Services/Invoices/InvoiceTuitionServiceTest.php @@ -15,9 +15,9 @@ class InvoiceTuitionServiceTest extends TestCase public function test_calculate_tuition_fee_counts_regular_and_youth(): void { DB::table('classSection')->insert([ - ['class_section_id' => 1, 'class_section_name' => '1', 'class_id' => 1], - ['class_section_id' => 2, 'class_section_name' => '2', 'class_id' => 2], - ['class_section_id' => 3, 'class_section_name' => '10', 'class_id' => 10], + ['class_section_id' => 1, 'class_section_name' => '1', 'class_id' => 1, 'semester' => 'Fall', 'school_year' => '2025-2026'], + ['class_section_id' => 2, 'class_section_name' => '2', 'class_id' => 2, 'semester' => 'Fall', 'school_year' => '2025-2026'], + ['class_section_id' => 3, 'class_section_name' => '10', 'class_id' => 10, 'semester' => 'Fall', 'school_year' => '2025-2026'], ]); $grades = new InvoiceGradeService(9); @@ -37,8 +37,8 @@ class InvoiceTuitionServiceTest extends TestCase public function test_refund_deadline_includes_withdrawn_when_passed(): void { DB::table('classSection')->insert([ - ['class_section_id' => 1, 'class_section_name' => '1', 'class_id' => 1], - ['class_section_id' => 2, 'class_section_name' => '2', 'class_id' => 2], + ['class_section_id' => 1, 'class_section_name' => '1', 'class_id' => 1, 'semester' => 'Fall', 'school_year' => '2025-2026'], + ['class_section_id' => 2, 'class_section_name' => '2', 'class_id' => 2, 'semester' => 'Fall', 'school_year' => '2025-2026'], ]); $grades = new InvoiceGradeService(9);