fix logic and tests, update docker CI file

This commit is contained in:
root
2026-03-09 15:58:44 -04:00
parent 1cb3573d4b
commit 79e44fe037
188 changed files with 1776 additions and 524 deletions
@@ -3,10 +3,11 @@
namespace App\Http\Controllers\Api\Administrator; namespace App\Http\Controllers\Api\Administrator;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Administrator\SubmitAdministratorAbsenceRequest;
use App\Services\Administrator\AdministratorAbsenceService; use App\Services\Administrator\AdministratorAbsenceService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
class AdministratorAbsenceController extends Controller class AdministratorAbsenceController extends Controller
{ {
@@ -25,13 +26,42 @@ class AdministratorAbsenceController extends Controller
return response()->json($this->service->getAbsenceFormData($userId)); return response()->json($this->service->getAbsenceFormData($userId));
} }
public function store(SubmitAdministratorAbsenceRequest $request): JsonResponse public function store(Request $request): JsonResponse
{ {
$userId = (int) Auth::id(); $userId = (int) Auth::id();
if ($userId <= 0) { if ($userId <= 0) {
return response()->json(['message' => 'Please log in first.'], 401); 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); $result = $this->service->submit($request, $userId);
return response()->json([ return response()->json([
@@ -3,11 +3,12 @@
namespace App\Http\Controllers\Api\Administrator; namespace App\Http\Controllers\Api\Administrator;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Administrator\UpdateEnrollmentStatusesRequest;
use App\Services\Administrator\AdministratorEnrollmentService; use App\Services\Administrator\AdministratorEnrollmentService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class AdministratorEnrollmentController extends Controller class AdministratorEnrollmentController extends Controller
{ {
@@ -30,14 +31,53 @@ class AdministratorEnrollmentController extends Controller
return response()->json($this->service->showNewStudentsData()); return response()->json($this->service->showNewStudentsData());
} }
public function updateStatuses(UpdateEnrollmentStatusesRequest $request): JsonResponse public function updateStatuses(Request $request): JsonResponse
{ {
$editorUserId = (int) Auth::id(); $editorUserId = (int) Auth::id();
if ($editorUserId <= 0) { if ($editorUserId <= 0) {
return response()->json(['message' => 'Please log in first.'], 401); 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( $result = $this->service->updateStatuses(
(array) ($data['enrollment_status'] ?? []), (array) ($data['enrollment_status'] ?? []),
$editorUserId $editorUserId
@@ -3,10 +3,10 @@
namespace App\Http\Controllers\Api\Administrator; namespace App\Http\Controllers\Api\Administrator;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Administrator\SaveAdminNotificationSubjectsRequest;
use App\Http\Requests\Administrator\SavePrintRecipientsRequest;
use App\Services\Administrator\AdministratorNotificationService; use App\Services\Administrator\AdministratorNotificationService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class AdministratorNotificationController extends Controller class AdministratorNotificationController extends Controller
{ {
@@ -20,9 +20,31 @@ class AdministratorNotificationController extends Controller
return response()->json($this->service->notificationsAlertsData()); 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( $result = $this->service->saveNotificationSubjects(
(array) ($data['subjects'] ?? []) (array) ($data['subjects'] ?? [])
); );
@@ -35,9 +57,31 @@ class AdministratorNotificationController extends Controller
return response()->json($this->service->printNotificationRecipientsData()); 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( $result = $this->service->savePrintNotificationRecipients(
(array) ($data['notify'] ?? []) (array) ($data['notify'] ?? [])
); );
@@ -3,10 +3,11 @@
namespace App\Http\Controllers\Api\Administrator; namespace App\Http\Controllers\Api\Administrator;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Administrator\SendTeacherSubmissionNotificationsRequest;
use App\Services\Administrator\AdministratorTeacherSubmissionService; use App\Services\Administrator\AdministratorTeacherSubmissionService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
class AdministratorTeacherSubmissionController extends Controller class AdministratorTeacherSubmissionController extends Controller
{ {
@@ -20,13 +21,30 @@ class AdministratorTeacherSubmissionController extends Controller
return response()->json($this->service->report()); return response()->json($this->service->report());
} }
public function notify(SendTeacherSubmissionNotificationsRequest $request): JsonResponse public function notify(Request $request): JsonResponse
{ {
$adminId = (int) Auth::id(); $adminId = (int) Auth::id();
if ($adminId <= 0) { if ($adminId <= 0) {
return response()->json(['message' => 'Please log in first.'], 401); 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); $result = $this->service->sendNotifications($request, $adminId);
return response()->json([ return response()->json([
@@ -3,12 +3,12 @@
namespace App\Http\Controllers\Api\Assignment; namespace App\Http\Controllers\Api\Assignment;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Assignment\StoreAssignmentRequest;
use App\Http\Resources\Assignment\AssignmentOverviewResource; use App\Http\Resources\Assignment\AssignmentOverviewResource;
use App\Http\Resources\Assignment\AssignmentSectionResource; use App\Http\Resources\Assignment\AssignmentSectionResource;
use App\Services\Assignment\AssignmentService; use App\Services\Assignment\AssignmentService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class AssignmentApiController extends Controller 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( $assignment = $this->assignmentService->storeAssignment(
data: $request->validated(), data: $validator->validated(),
updatedBy: $request->user()?->id updatedBy: $request->user()?->id
); );
@@ -3,11 +3,10 @@
namespace App\Http\Controllers\Api\Attendance; namespace App\Http\Controllers\Api\Attendance;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\AttendanceCommentTemplate\StoreAttendanceCommentTemplateRequest;
use App\Http\Requests\AttendanceCommentTemplate\UpdateAttendanceCommentTemplateRequest;
use App\Services\AttendanceCommentTemplateService; use App\Services\AttendanceCommentTemplateService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class AttendanceCommentTemplateController extends Controller 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([ return response()->json([
'status' => 'success', 'status' => 'success',
@@ -60,9 +78,31 @@ class AttendanceCommentTemplateController extends Controller
], 201); ], 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([ return response()->json([
'status' => 'success', 'status' => 'success',
@@ -3,14 +3,10 @@
namespace App\Http\Controllers\Api\AttendanceTracking; namespace App\Http\Controllers\Api\AttendanceTracking;
use App\Http\Controllers\Controller; 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 App\Services\AttendanceTracking\AttendanceTrackingService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class AttendanceTrackingController extends Controller 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( return response()->json(
$result, $result,
@@ -80,13 +93,28 @@ class AttendanceTrackingController extends Controller
return response()->json($result); 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( $result = $this->service->compose(
(int) $request->validated('student_id'), (int) ($data['student_id'] ?? 0),
(string) $request->validated('code', 'ABS_1'), (string) ($data['code'] ?? 'ABS_1'),
(string) $request->validated('variant', 'default'), (string) ($data['variant'] ?? 'default'),
$request->validated('incident_date'), $data['incident_date'] ?? null,
); );
return response()->json( 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( return response()->json(
$result, $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( return response()->json(
$result, $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( return response()->json(
$result, $result,
@@ -3,11 +3,12 @@
namespace App\Http\Controllers\Api\Auth; namespace App\Http\Controllers\Api\Auth;
use App\Http\Controllers\Api\BaseApiController; use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Auth\RegisterRequest;
use App\Http\Resources\Auth\RegisterResource; use App\Http\Resources\Auth\RegisterResource;
use App\Services\Auth\RegistrationCaptchaService; use App\Services\Auth\RegistrationCaptchaService;
use App\Services\Auth\RegistrationService; use App\Services\Auth\RegistrationService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class RegisterController extends BaseApiController 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') { if (app()->runningUnitTests() && $request->header('X-Debug-Request') === '1') {
return response()->json([ 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'])) { if (empty($result['ok'])) {
$code = $result['code'] ?? 'registration_failed'; $code = $result['code'] ?? 'registration_failed';
@@ -3,13 +3,12 @@
namespace App\Http\Controllers\Api\Badges; namespace App\Http\Controllers\Api\Badges;
use App\Http\Controllers\Controller; 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\BadgeFormDataService;
use App\Services\Badges\BadgePdfService; use App\Services\Badges\BadgePdfService;
use App\Services\Badges\BadgePrintLogService; use App\Services\Badges\BadgePrintLogService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Throwable; use Throwable;
class BadgeController extends Controller 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( return $this->badgePdfService->generate(
userIds: $request->input('user_ids', []), userIds: $data['user_ids'] ?? [],
schoolYear: $request->input('school_year'), schoolYear: $data['school_year'] ?? null,
rolesMap: $request->input('roles', []), rolesMap: $data['roles'] ?? [],
classesMap: $request->input('classes', []), classesMap: $data['classes'] ?? [],
actorId: optional($request->user())->id actorId: optional($request->user())->id
); );
} }
public function printStatus(BadgePrintStatusRequest $request): JsonResponse public function printStatus(Request $request): JsonResponse
{ {
try { try {
return response()->json([ return response()->json([
'ok' => true, 'ok' => true,
'data' => $this->badgePrintLogService->getStatus( 'data' => $this->badgePrintLogService->getStatus(
$request->normalizedUserIds(), $this->parseUserIds($request),
$request->input('school_year') $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 { 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( $inserted = $this->badgePrintLogService->log(
userIds: $request->input('user_ids', []), userIds: $data['user_ids'] ?? [],
actorId: optional($request->user())->id, actorId: optional($request->user())->id,
schoolYear: $request->input('school_year'), schoolYear: $data['school_year'] ?? null,
rolesMap: $request->input('roles', []), rolesMap: $data['roles'] ?? [],
classesMap: $request->input('classes', []) classesMap: $data['classes'] ?? []
); );
return response()->json([ 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([ return response()->json([
'ok' => true, 'ok' => true,
'data' => $this->badgeFormDataService->build( 'data' => $this->badgeFormDataService->build(
schoolYear: $request->input('school_year'), schoolYear: $request->input('school_year'),
selectedUserIds: $request->normalizedUserIds(), selectedUserIds: $this->parseUserIds($request),
activeRole: $request->input('active_role') activeRole: $request->input('active_role')
), ),
]); ]);
} }
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));
}
} }
@@ -3,13 +3,13 @@
namespace App\Http\Controllers\Api\Finance; namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\BaseApiController; 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\FeeRefundResource;
use App\Http\Resources\Fees\FeeTuitionTotalResource; use App\Http\Resources\Fees\FeeTuitionTotalResource;
use App\Services\Fees\FeeRefundCalculatorService; use App\Services\Fees\FeeRefundCalculatorService;
use App\Services\Fees\FeeStudentFeeService; use App\Services\Fees\FeeStudentFeeService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class FeeCalculationController extends BaseApiController class FeeCalculationController extends BaseApiController
{ {
@@ -20,9 +20,26 @@ class FeeCalculationController extends BaseApiController
parent::__construct(); 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']); $result = $this->refunds->calculateRefund($payload['students'], (int) $payload['parent_id']);
return response()->json([ 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']); $total = $this->tuition->totalTuitionFee($payload['students']);
return response()->json([ return response()->json([
@@ -3,7 +3,6 @@
namespace App\Http\Controllers\Api\Finance; namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\BaseApiController; use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Invoices\InvoiceGenerateRequest;
use App\Http\Requests\Invoices\InvoiceManagementRequest; use App\Http\Requests\Invoices\InvoiceManagementRequest;
use App\Http\Requests\Invoices\InvoiceParentRequest; use App\Http\Requests\Invoices\InvoiceParentRequest;
use App\Http\Requests\Invoices\InvoiceStatusRequest; use App\Http\Requests\Invoices\InvoiceStatusRequest;
@@ -15,6 +14,8 @@ use App\Services\Invoices\InvoiceManagementService;
use App\Services\Invoices\InvoicePaymentService; use App\Services\Invoices\InvoicePaymentService;
use App\Services\Invoices\InvoicePdfService; use App\Services\Invoices\InvoicePdfService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpFoundation\StreamedResponse;
class InvoiceController extends BaseApiController 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); $result = $this->generation->generateInvoice((int) $payload['parent_id'], $payload['school_year'] ?? null);
if (empty($result['ok'])) { if (empty($result['ok'])) {
@@ -4,13 +4,13 @@ namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\BaseApiController; use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Payments\PaymentByParentRequest; 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\Http\Resources\Payments\PaymentResource;
use App\Services\Payments\PaymentBalanceService; use App\Services\Payments\PaymentBalanceService;
use App\Services\Payments\PaymentLookupService; use App\Services\Payments\PaymentLookupService;
use App\Services\Payments\PaymentPlanService; use App\Services\Payments\PaymentPlanService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class PaymentController extends BaseApiController class PaymentController extends BaseApiController
{ {
@@ -22,9 +22,30 @@ class PaymentController extends BaseApiController
parent::__construct(); 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); $payment = $this->planService->createPlan($payload);
return response()->json([ 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']); $updated = $this->balanceService->updateBalance($paymentId, (float) $payload['paid_amount']);
if (!$updated) { if (!$updated) {
@@ -4,9 +4,10 @@ namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\BaseApiController; use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Payments\PaymentEventChargesListRequest; use App\Http\Requests\Payments\PaymentEventChargesListRequest;
use App\Http\Requests\Payments\PaymentEventChargesStoreRequest;
use App\Services\Payments\PaymentEventChargesService; use App\Services\Payments\PaymentEventChargesService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class PaymentEventChargesController extends BaseApiController class PaymentEventChargesController extends BaseApiController
{ {
@@ -23,9 +24,29 @@ class PaymentEventChargesController extends BaseApiController
return response()->json(['ok' => true] + $data); 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); $userId = (int) (auth()->id() ?? 0);
$count = $this->service->addCharges($payload, $userId); $count = $this->service->addCharges($payload, $userId);
@@ -4,12 +4,11 @@ namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\BaseApiController; use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Payments\PaymentManualEditRequest; 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\Http\Requests\Payments\PaymentManualUpdateRequest;
use App\Services\Payments\PaymentManualService; use App\Services\Payments\PaymentManualService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class PaymentManualController extends BaseApiController class PaymentManualController extends BaseApiController
{ {
@@ -18,9 +17,22 @@ class PaymentManualController extends BaseApiController
parent::__construct(); 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( $data = $this->service->search(
(string) ($payload['search_term'] ?? ''), (string) ($payload['search_term'] ?? ''),
$payload['school_year'] ?? null $payload['school_year'] ?? null
@@ -29,9 +41,21 @@ class PaymentManualController extends BaseApiController
return response()->json(['ok' => true] + $data); 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'] ?? '')); $items = $this->service->suggest((string) ($payload['q'] ?? ''));
return response()->json(['ok' => true, 'items' => $items]); return response()->json(['ok' => true, 'items' => $items]);
@@ -3,11 +3,11 @@
namespace App\Http\Controllers\Api\Finance; namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\BaseApiController; use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Payments\PaymentTransactionCreateRequest;
use App\Http\Resources\Payments\PaymentTransactionResource; use App\Http\Resources\Payments\PaymentTransactionResource;
use App\Services\Payments\PaymentTransactionService; use App\Services\Payments\PaymentTransactionService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class PaymentTransactionController extends BaseApiController class PaymentTransactionController extends BaseApiController
{ {
@@ -16,9 +16,31 @@ class PaymentTransactionController extends BaseApiController
parent::__construct(); 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); $transaction = $this->service->create($payload);
return response()->json([ return response()->json([
@@ -4,8 +4,6 @@ namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\BaseApiController; use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\PurchaseOrders\PurchaseOrderIndexRequest; 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\PurchaseOrderItemResource;
use App\Http\Resources\PurchaseOrders\PurchaseOrderResource; use App\Http\Resources\PurchaseOrders\PurchaseOrderResource;
use App\Models\PurchaseOrder; use App\Models\PurchaseOrder;
@@ -14,6 +12,8 @@ use App\Services\PurchaseOrders\PurchaseOrderQueryService;
use App\Services\PurchaseOrders\PurchaseOrderReceiveService; use App\Services\PurchaseOrders\PurchaseOrderReceiveService;
use App\Services\PurchaseOrders\PurchaseOrderStatusService; use App\Services\PurchaseOrders\PurchaseOrderStatusService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use RuntimeException; use RuntimeException;
class PurchaseOrderController extends BaseApiController 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 { try {
$po = $this->createService->create($payload); $po = $this->createService->create($payload);
@@ -81,9 +113,23 @@ class PurchaseOrderController extends BaseApiController
], 201); ], 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); $po = PurchaseOrder::query()->find($id);
if (!$po) { if (!$po) {
@@ -6,17 +6,13 @@ use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Files\FileNameRequest; use App\Http\Requests\Files\FileNameRequest;
use App\Http\Requests\Reimbursements\ReimbursementBatchAdminFileRequest; use App\Http\Requests\Reimbursements\ReimbursementBatchAdminFileRequest;
use App\Http\Requests\Reimbursements\ReimbursementBatchAssignmentRequest; use App\Http\Requests\Reimbursements\ReimbursementBatchAssignmentRequest;
use App\Http\Requests\Reimbursements\ReimbursementBatchCreateRequest;
use App\Http\Requests\Reimbursements\ReimbursementBatchEmailRequest; use App\Http\Requests\Reimbursements\ReimbursementBatchEmailRequest;
use App\Http\Requests\Reimbursements\ReimbursementBatchLockRequest; use App\Http\Requests\Reimbursements\ReimbursementBatchLockRequest;
use App\Http\Requests\Reimbursements\ReimbursementExportBatchRequest; use App\Http\Requests\Reimbursements\ReimbursementExportBatchRequest;
use App\Http\Requests\Reimbursements\ReimbursementExportRequest; use App\Http\Requests\Reimbursements\ReimbursementExportRequest;
use App\Http\Requests\Reimbursements\ReimbursementIndexRequest; use App\Http\Requests\Reimbursements\ReimbursementIndexRequest;
use App\Http\Requests\Reimbursements\ReimbursementMarkDonationRequest;
use App\Http\Requests\Reimbursements\ReimbursementProcessRequest; use App\Http\Requests\Reimbursements\ReimbursementProcessRequest;
use App\Http\Requests\Reimbursements\ReimbursementStoreRequest;
use App\Http\Requests\Reimbursements\ReimbursementUnderProcessingRequest; use App\Http\Requests\Reimbursements\ReimbursementUnderProcessingRequest;
use App\Http\Requests\Reimbursements\ReimbursementUpdateRequest;
use App\Http\Resources\Reimbursements\ReimbursementBatchResource; use App\Http\Resources\Reimbursements\ReimbursementBatchResource;
use App\Http\Resources\Reimbursements\ReimbursementExpenseResource; use App\Http\Resources\Reimbursements\ReimbursementExpenseResource;
use App\Http\Resources\Reimbursements\ReimbursementRecipientResource; use App\Http\Resources\Reimbursements\ReimbursementRecipientResource;
@@ -37,6 +33,7 @@ use App\Services\Reimbursements\ReimbursementQueryService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Illuminate\Support\Facades\Validator;
use RuntimeException; use RuntimeException;
class ReimbursementController extends BaseApiController 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); $userId = (int) (auth()->id() ?? 0);
try { try {
@@ -87,9 +96,21 @@ class ReimbursementController extends BaseApiController
return response()->json(['ok' => true]); 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); $userId = (int) (auth()->id() ?? 0);
$schoolYear = $this->context->getSchoolYear(); $schoolYear = $this->context->getSchoolYear();
$semester = $this->context->getSemester(); $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); $userId = (int) (auth()->id() ?? 0);
$receiptName = null; $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); $reimb = Reimbursement::query()->find($id);
if (!$reimb) { if (!$reimb) {
return response()->json(['ok' => false, 'message' => 'Reimbursement not found.'], 404); 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); $userId = (int) (auth()->id() ?? 0);
$receiptName = $reimb->receipt_path; $receiptName = $reimb->receipt_path;
@@ -7,7 +7,6 @@ use App\Http\Requests\Incidents\IncidentCancelRequest;
use App\Http\Requests\Incidents\IncidentCloseRequest; use App\Http\Requests\Incidents\IncidentCloseRequest;
use App\Http\Requests\Incidents\IncidentListRequest; use App\Http\Requests\Incidents\IncidentListRequest;
use App\Http\Requests\Incidents\IncidentStateRequest; use App\Http\Requests\Incidents\IncidentStateRequest;
use App\Http\Requests\Incidents\IncidentStoreRequest;
use App\Http\Resources\Incidents\IncidentAnalysisStudentResource; use App\Http\Resources\Incidents\IncidentAnalysisStudentResource;
use App\Http\Resources\Incidents\IncidentGradeResource; use App\Http\Resources\Incidents\IncidentGradeResource;
use App\Http\Resources\Incidents\IncidentResource; use App\Http\Resources\Incidents\IncidentResource;
@@ -16,6 +15,8 @@ use App\Services\Incidents\CurrentIncidentService;
use App\Services\Incidents\IncidentAnalysisService; use App\Services\Incidents\IncidentAnalysisService;
use App\Services\Incidents\IncidentHistoryService; use App\Services\Incidents\IncidentHistoryService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class IncidentController extends BaseApiController 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)); $result = $this->currentService->addIncident($payload, (int) (auth()->id() ?? 0));
if (empty($result['ok'])) { 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( $result = $this->currentService->closeIncident(
$incidentId, $incidentId,
(string) $payload['state_description'], (string) $payload['state_description'],
@@ -3,13 +3,14 @@
namespace App\Http\Controllers\Api\Scores; namespace App\Http\Controllers\Api\Scores;
use App\Http\Controllers\Api\BaseApiController; use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Scores\ScoreClassRequest;
use App\Http\Requests\Scores\ScoreUpdateRequest; use App\Http\Requests\Scores\ScoreUpdateRequest;
use App\Http\Resources\Scores\ScoreStudentResource; use App\Http\Resources\Scores\ScoreStudentResource;
use App\Models\Student; use App\Models\Student;
use App\Services\Scores\ExamScoreService; use App\Services\Scores\ExamScoreService;
use App\Services\Scores\SemesterScoreService; use App\Services\Scores\SemesterScoreService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class FinalController extends BaseApiController class FinalController extends BaseApiController
{ {
@@ -20,9 +21,23 @@ class FinalController extends BaseApiController
parent::__construct(); 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); $data = $this->service->list('final_exam', (int) $payload['class_section_id'], $payload['semester'] ?? null, $payload['school_year'] ?? null);
return response()->json([ return response()->json([
@@ -4,13 +4,14 @@ namespace App\Http\Controllers\Api\Scores;
use App\Http\Controllers\Api\BaseApiController; use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Scores\ScoreAddColumnRequest; use App\Http\Requests\Scores\ScoreAddColumnRequest;
use App\Http\Requests\Scores\ScoreClassRequest;
use App\Http\Requests\Scores\ScoreUpdateRequest; use App\Http\Requests\Scores\ScoreUpdateRequest;
use App\Http\Resources\Scores\ScoreStudentResource; use App\Http\Resources\Scores\ScoreStudentResource;
use App\Models\Student; use App\Models\Student;
use App\Services\Scores\HomeworkScoreService; use App\Services\Scores\HomeworkScoreService;
use App\Services\Scores\SemesterScoreService; use App\Services\Scores\SemesterScoreService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class HomeworkController extends BaseApiController class HomeworkController extends BaseApiController
{ {
@@ -21,9 +22,23 @@ class HomeworkController extends BaseApiController
parent::__construct(); 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( $data = $this->service->list(
(int) $payload['class_section_id'], (int) $payload['class_section_id'],
$payload['semester'] ?? null, $payload['semester'] ?? null,
@@ -3,13 +3,14 @@
namespace App\Http\Controllers\Api\Scores; namespace App\Http\Controllers\Api\Scores;
use App\Http\Controllers\Api\BaseApiController; use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Scores\ScoreClassRequest;
use App\Http\Requests\Scores\ScoreUpdateRequest; use App\Http\Requests\Scores\ScoreUpdateRequest;
use App\Http\Resources\Scores\ScoreStudentResource; use App\Http\Resources\Scores\ScoreStudentResource;
use App\Models\Student; use App\Models\Student;
use App\Services\Scores\ExamScoreService; use App\Services\Scores\ExamScoreService;
use App\Services\Scores\SemesterScoreService; use App\Services\Scores\SemesterScoreService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class MidtermController extends BaseApiController class MidtermController extends BaseApiController
{ {
@@ -20,9 +21,23 @@ class MidtermController extends BaseApiController
parent::__construct(); 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); $data = $this->service->list('midterm_exam', (int) $payload['class_section_id'], $payload['semester'] ?? null, $payload['school_year'] ?? null);
return response()->json([ return response()->json([
@@ -3,13 +3,14 @@
namespace App\Http\Controllers\Api\Scores; namespace App\Http\Controllers\Api\Scores;
use App\Http\Controllers\Api\BaseApiController; use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Scores\ScoreClassRequest;
use App\Http\Requests\Scores\ScoreUpdateRequest; use App\Http\Requests\Scores\ScoreUpdateRequest;
use App\Http\Resources\Scores\ScoreStudentResource; use App\Http\Resources\Scores\ScoreStudentResource;
use App\Models\Student; use App\Models\Student;
use App\Services\Scores\ParticipationScoreService; use App\Services\Scores\ParticipationScoreService;
use App\Services\Scores\SemesterScoreService; use App\Services\Scores\SemesterScoreService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class ParticipationController extends BaseApiController class ParticipationController extends BaseApiController
{ {
@@ -20,9 +21,23 @@ class ParticipationController extends BaseApiController
parent::__construct(); 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( $data = $this->service->list(
(int) $payload['class_section_id'], (int) $payload['class_section_id'],
$payload['semester'] ?? null, $payload['semester'] ?? null,
@@ -4,13 +4,14 @@ namespace App\Http\Controllers\Api\Scores;
use App\Http\Controllers\Api\BaseApiController; use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Scores\ScoreAddColumnRequest; use App\Http\Requests\Scores\ScoreAddColumnRequest;
use App\Http\Requests\Scores\ScoreClassRequest;
use App\Http\Requests\Scores\ScoreUpdateRequest; use App\Http\Requests\Scores\ScoreUpdateRequest;
use App\Http\Resources\Scores\ScoreStudentResource; use App\Http\Resources\Scores\ScoreStudentResource;
use App\Models\Student; use App\Models\Student;
use App\Services\Scores\ProjectScoreService; use App\Services\Scores\ProjectScoreService;
use App\Services\Scores\SemesterScoreService; use App\Services\Scores\SemesterScoreService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class ProjectController extends BaseApiController class ProjectController extends BaseApiController
{ {
@@ -21,9 +22,23 @@ class ProjectController extends BaseApiController
parent::__construct(); 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( $data = $this->service->list(
(int) $payload['class_section_id'], (int) $payload['class_section_id'],
$payload['semester'] ?? null, $payload['semester'] ?? null,
@@ -4,13 +4,14 @@ namespace App\Http\Controllers\Api\Scores;
use App\Http\Controllers\Api\BaseApiController; use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Scores\ScoreAddColumnRequest; use App\Http\Requests\Scores\ScoreAddColumnRequest;
use App\Http\Requests\Scores\ScoreClassRequest;
use App\Http\Requests\Scores\ScoreUpdateRequest; use App\Http\Requests\Scores\ScoreUpdateRequest;
use App\Http\Resources\Scores\ScoreStudentResource; use App\Http\Resources\Scores\ScoreStudentResource;
use App\Models\Student; use App\Models\Student;
use App\Services\Scores\QuizScoreService; use App\Services\Scores\QuizScoreService;
use App\Services\Scores\SemesterScoreService; use App\Services\Scores\SemesterScoreService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class QuizController extends BaseApiController class QuizController extends BaseApiController
{ {
@@ -21,9 +22,23 @@ class QuizController extends BaseApiController
parent::__construct(); 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( $data = $this->service->list(
(int) $payload['class_section_id'], (int) $payload['class_section_id'],
$payload['semester'] ?? null, $payload['semester'] ?? null,
@@ -7,6 +7,39 @@ use Illuminate\Validation\Rule;
class UpdateEnrollmentStatusesRequest extends ApiFormRequest 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 public function authorize(): bool
{ {
return auth()->check(); return auth()->check();
@@ -1,10 +1,10 @@
<?php <?php
namespace App\Http\Requests; namespace App\Http\Requests\Assignment;
use Illuminate\Foundation\Http\FormRequest; use App\Http\Requests\ApiFormRequest;
class StoreAssignmentRequest extends FormRequest class StoreAssignmentRequest extends ApiFormRequest
{ {
public function authorize(): bool public function authorize(): bool
{ {
@@ -15,7 +15,7 @@ class StoreAssignmentRequest extends FormRequest
{ {
return [ return [
'student_id' => ['required', 'integer', 'exists:students,id'], 'student_id' => ['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'], 'semester' => ['required', 'string', 'max:50'],
'school_year' => ['required', 'string', 'max:50'], 'school_year' => ['required', 'string', 'max:50'],
'description' => ['nullable', 'string'], 'description' => ['nullable', 'string'],
@@ -2,9 +2,9 @@
namespace App\Http\Requests\AttendanceTracking; 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 public function authorize(): bool
{ {
@@ -2,9 +2,9 @@
namespace App\Http\Requests\AttendanceTracking; 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 public function authorize(): bool
{ {
@@ -2,9 +2,9 @@
namespace App\Http\Requests\AttendanceTracking; 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 public function authorize(): bool
{ {
@@ -2,9 +2,9 @@
namespace App\Http\Requests\AttendanceTracking; 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 public function authorize(): bool
{ {
@@ -2,9 +2,9 @@
namespace App\Http\Requests\AttendanceTracking; 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 public function authorize(): bool
{ {
@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Resources; namespace App\Http\Resources\Assignment;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\JsonResource;
@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Resources; namespace App\Http\Resources\Assignment;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\JsonResource;
@@ -0,0 +1,25 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class TeacherSubmissionReminderMail extends Mailable
{
use Queueable;
use SerializesModels;
public function __construct(
protected string $subjectLine,
protected string $htmlBody
) {
}
public function build(): self
{
return $this->subject($this->subjectLine)
->html($this->htmlBody);
}
}
+12 -11
View File
@@ -68,7 +68,7 @@ class AttendanceDay extends BaseModel
$q = static::query() $q = static::query()
->select('id') ->select('id')
->where('class_section_id', $classSectionId) ->where('class_section_id', $classSectionId)
->where('date', $date) ->whereDate('date', $date)
->where(function ($w) { ->where(function ($w) {
$w->where('status', 'published') $w->where('status', 'published')
->orWhere('status', 'finalized'); // legacy ->orWhere('status', 'finalized'); // legacy
@@ -91,7 +91,7 @@ class AttendanceDay extends BaseModel
): ?self { ): ?self {
return static::query() return static::query()
->where('class_section_id', $classSectionId) ->where('class_section_id', $classSectionId)
->where('date', $date) ->whereDate('date', $date)
->where('semester', $semester) ->where('semester', $semester)
->where('school_year', $schoolYear) ->where('school_year', $schoolYear)
->first(); ->first();
@@ -112,11 +112,12 @@ class AttendanceDay extends BaseModel
if ($row) return $row; if ($row) return $row;
$now = now(); $now = now();
$normalizedDate = Carbon::parse($date)->toDateString();
try { try {
return static::create([ return static::create([
'class_section_id' => $classSectionId, 'class_section_id' => $classSectionId,
'date' => $date, 'date' => $normalizedDate,
'status' => 'draft', 'status' => 'draft',
'submitted_by' => null, 'submitted_by' => null,
'submitted_at' => null, 'submitted_at' => null,
@@ -142,15 +143,15 @@ class AttendanceDay extends BaseModel
/** /**
* DEPRECATED legacy finalize => submit (teacher submit). * 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). * 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(); $now = now();
@@ -165,17 +166,17 @@ class AttendanceDay extends BaseModel
$payload['auto_publish_at'] = $autoPublishAt; $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. * Admin publish (hard lock): status => published.
*/ */
public static function publish(int $id, int $userId): bool public function publish(int $userId): bool
{ {
$now = now(); $now = now();
return static::query()->whereKey($id)->update([ return static::query()->whereKey($this->id)->update([
'status' => 'published', 'status' => 'published',
'published_by' => $userId, 'published_by' => $userId,
'published_at' => $now, 'published_at' => $now,
@@ -186,7 +187,7 @@ class AttendanceDay extends BaseModel
/** /**
* Admin reopen a day back to 'draft' (default) or 'submitted'. * 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)) { if (!in_array($toStatus, ['draft', 'submitted'], true)) {
throw new \InvalidArgumentException('toStatus must be "draft" or "submitted".'); throw new \InvalidArgumentException('toStatus must be "draft" or "submitted".');
@@ -194,7 +195,7 @@ class AttendanceDay extends BaseModel
$now = now(); $now = now();
return static::query()->whereKey($id)->update([ return static::query()->whereKey($this->id)->update([
'status' => $toStatus, 'status' => $toStatus,
'reopened_by' => $userId, 'reopened_by' => $userId,
'reopened_at' => $now, 'reopened_at' => $now,
+5 -3
View File
@@ -30,7 +30,7 @@ class AttendanceEmailTemplate extends BaseModel
* Fetch a template by code and variant, falling back to 'default' if needed. * Fetch a template by code and variant, falling back to 'default' if needed.
* Active only. * 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() $row = static::query()
->where('code', $code) ->where('code', $code)
@@ -39,13 +39,15 @@ class AttendanceEmailTemplate extends BaseModel
->first(); ->first();
if ($row) { if ($row) {
return $row; return $row->toArray();
} }
return static::query() $fallback = static::query()
->where('code', $code) ->where('code', $code)
->where('variant', 'default') ->where('variant', 'default')
->where('is_active', 1) ->where('is_active', 1)
->first(); ->first();
return $fallback?->toArray();
} }
} }
+12
View File
@@ -3,10 +3,12 @@
namespace App\Models; namespace App\Models;
use App\Models\BaseModel; use App\Models\BaseModel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
class ClassSection extends BaseModel class ClassSection extends BaseModel
{ {
use HasFactory;
// CI table name is "classSection" // CI table name is "classSection"
protected $table = 'classSection'; protected $table = 'classSection';
@@ -28,6 +30,16 @@ class ClassSection extends BaseModel
'class_section_id' => 'integer', '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) * Relationships (optional)
* ========================= */ * ========================= */
+5
View File
@@ -28,6 +28,11 @@ class CommunicationLog extends BaseModel
'metadata', 'metadata',
]; ];
public function getFillable(): array
{
return $this->fillable;
}
protected $casts = [ protected $casts = [
'student_id' => 'integer', 'student_id' => 'integer',
'family_id' => 'integer', 'family_id' => 'integer',
+16
View File
@@ -23,8 +23,24 @@ class EmailTemplate extends BaseModel
protected $casts = [ protected $casts = [
'is_active' => 'boolean', '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(): * Equivalent of CI getActiveTemplates():
* is_active=1, orderBy template_key ASC * is_active=1, orderBy template_key ASC
+5
View File
@@ -40,6 +40,11 @@ class PurchaseOrder extends BaseModel
'deleted_at' => 'datetime', 'deleted_at' => 'datetime',
]; ];
public function getFillable(): array
{
return $this->fillable;
}
/* ============================================================ /* ============================================================
* Status (enum-like) * Status (enum-like)
* ============================================================ * ============================================================
+5
View File
@@ -30,6 +30,11 @@ class PurchaseOrderItem extends BaseModel
'updated_at' => 'datetime', 'updated_at' => 'datetime',
]; ];
public function getFillable(): array
{
return $this->fillable;
}
/* ============================================================ /* ============================================================
* Relationships (optional but recommended) * Relationships (optional but recommended)
* ============================================================ * ============================================================
+6 -1
View File
@@ -12,7 +12,7 @@ class Stats extends BaseModel
* If your stats table has created_at/updated_at, keep true (default). * If your stats table has created_at/updated_at, keep true (default).
* If it doesn't, set to false. * If it doesn't, set to false.
*/ */
public $timestamps = false; public $timestamps = true;
protected $fillable = [ protected $fillable = [
'students', 'students',
@@ -28,6 +28,11 @@ class Stats extends BaseModel
'users' => 'integer', 'users' => 'integer',
]; ];
public function getFillable(): array
{
return $this->fillable;
}
/** /**
* CI-compatible: getStats() * CI-compatible: getStats()
* Returns all rows. * Returns all rows.
+3 -1
View File
@@ -4,12 +4,14 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use App\Models\BaseModel; use App\Models\BaseModel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
class Student extends BaseModel class Student extends BaseModel
{ {
use HasFactory;
protected $table = 'students'; protected $table = 'students';
/** /**
@@ -38,7 +40,7 @@ class Student extends BaseModel
]; ];
protected $casts = [ protected $casts = [
'school_id' => 'integer', 'school_id' => 'string',
'age' => 'integer', 'age' => 'integer',
'photo_consent' => 'boolean', 'photo_consent' => 'boolean',
'is_new' => 'boolean', 'is_new' => 'boolean',
+3 -3
View File
@@ -279,7 +279,7 @@ class TeacherClass extends BaseModel
->join('classSection as cs', 'cs.class_section_id', '=', 'tc.class_section_id') ->join('classSection as cs', 'cs.class_section_id', '=', 'tc.class_section_id')
->where('tc.teacher_id', $teacherId) ->where('tc.teacher_id', $teacherId)
->where('tc.school_year', $schoolYear) ->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() ->get()
->map(fn ($r) => (array) $r) ->map(fn ($r) => (array) $r)
->all(); ->all();
@@ -296,7 +296,7 @@ class TeacherClass extends BaseModel
->leftJoin('users as u', 'u.id', '=', 'tc.teacher_id') ->leftJoin('users as u', 'u.id', '=', 'tc.teacher_id')
->where('tc.class_section_id', $sectionCode) ->where('tc.class_section_id', $sectionCode)
->where('tc.school_year', $schoolYear) ->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') ->orderBy('u.firstname', 'asc')
->get() ->get()
->map(fn ($r) => (array) $r) ->map(fn ($r) => (array) $r)
@@ -324,7 +324,7 @@ class TeacherClass extends BaseModel
->leftJoin('users as u', 'u.id', '=', 'tc.teacher_id') ->leftJoin('users as u', 'u.id', '=', 'tc.teacher_id')
->where('tc.school_year', $schoolYear) ->where('tc.school_year', $schoolYear)
->orderBy('tc.class_section_id', 'asc') ->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'); ->orderBy('u.firstname', 'asc');
if (!empty($onlySectionCodes)) { if (!empty($onlySectionCodes)) {
+2
View File
@@ -3,6 +3,7 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -14,6 +15,7 @@ use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable class User extends Authenticatable
{ {
use HasApiTokens; use HasApiTokens;
use HasFactory;
protected $table = 'users'; protected $table = 'users';
+20
View File
@@ -7,6 +7,9 @@ use App\Services\Attendance\AttendanceRecordSyncService;
use App\Services\Attendance\SemesterRangeService; use App\Services\Attendance\SemesterRangeService;
use App\Services\Attendance\StudentAttendanceWriterService; use App\Services\Attendance\StudentAttendanceWriterService;
use App\Services\Attendance\TeacherAttendanceSubmissionService; 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\Foundation\Http\FormRequest;
use Illuminate\Routing\Redirector; use Illuminate\Routing\Redirector;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
@@ -20,6 +23,23 @@ class AppServiceProvider extends ServiceProvider
$this->app->singleton(AttendanceRecordSyncService::class); $this->app->singleton(AttendanceRecordSyncService::class);
$this->app->singleton(StudentAttendanceWriterService::class); $this->app->singleton(StudentAttendanceWriterService::class);
$this->app->singleton(TeacherAttendanceSubmissionService::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 public function boot(): void
@@ -4,7 +4,7 @@ namespace App\Services\Administrator;
use App\Models\Enrollment; use App\Models\Enrollment;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\RefundModel as Refund; use App\Models\Refund;
use App\Services\FeeCalculationService; use App\Services\FeeCalculationService;
class AdministratorEnrollmentRefundService class AdministratorEnrollmentRefundService
@@ -2,6 +2,7 @@
namespace App\Services\Administrator; namespace App\Services\Administrator;
use App\Mail\TeacherSubmissionReminderMail;
use App\Models\ClassSection; use App\Models\ClassSection;
use App\Models\TeacherSubmissionNotificationHistory; use App\Models\TeacherSubmissionNotificationHistory;
use App\Models\User; use App\Models\User;
@@ -88,9 +89,7 @@ class TeacherSubmissionNotificationService
if (!empty($email) && filter_var($email, FILTER_VALIDATE_EMAIL)) { if (!empty($email) && filter_var($email, FILTER_VALIDATE_EMAIL)) {
try { try {
Mail::html($body, function ($message) use ($email, $subject) { Mail::to($email)->send(new TeacherSubmissionReminderMail($subject, $body));
$message->to($email)->subject($subject);
});
$status = 'sent'; $status = 'sent';
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Teacher submission notification failed: ' . $e->getMessage()); Log::error('Teacher submission notification failed: ' . $e->getMessage());
@@ -163,7 +163,7 @@ class TeacherSubmissionReportService
->where('class_section_id', $classSectionId) ->where('class_section_id', $classSectionId)
->where('semester', $semester) ->where('semester', $semester)
->where('school_year', $schoolYear) ->where('school_year', $schoolYear)
->where('date', $today) ->whereDate('date', $today)
->first(); ->first();
$attendanceSubmitted = $attendanceRow $attendanceSubmitted = $attendanceRow
@@ -187,7 +187,7 @@ class TeacherSubmissionReportService
$midtermCommentStatus = $this->support->submissionStatus(count($midtermCommentStudents), $expected); $midtermCommentStatus = $this->support->submissionStatus(count($midtermCommentStudents), $expected);
$participationStatus = $this->support->submissionStatus(count($participationStudents), $expected); $participationStatus = $this->support->submissionStatus(count($participationStudents), $expected);
$ptapCommentStatus = $this->support->submissionStatus(count($ptapCommentStudents), $expected); $ptapCommentStatus = $this->support->submissionStatus(count($ptapCommentStudents), $expected);
$attendanceStatus = $this->support->attendanceStatus($attendanceSubmitted); $attendanceStatus = $this->support->attendanceStatus($attendanceSubmitted, $expected);
$statusDetails = [ $statusDetails = [
'midterm_score_status' => $midtermScoreStatus, 'midterm_score_status' => $midtermScoreStatus,
@@ -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 [ return [
'label' => $submitted ? 'Submitted' : 'Missing', 'label' => $submitted ? 'Submitted' : 'Missing',
'badge' => $submitted ? 'bg-success' : 'bg-danger', 'badge' => $submitted ? 'bg-success' : 'bg-danger',
@@ -20,7 +20,14 @@ class AssignmentDataLoaderService
->with([ ->with([
'teacher:id,firstname,lastname', '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() ->get()
->map(function ($row) { ->map(function ($row) {
$row->teacher_full_name = trim( $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', '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)) ->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(); ->get();
} }
} }
@@ -16,11 +16,11 @@ class AssignmentSectionService
return $this->classSection return $this->classSection
->newQuery() ->newQuery()
->whereIn('id', $sectionIds) ->whereIn('class_section_id', $sectionIds)
->get() ->get()
->mapWithKeys(function ($section) { ->mapWithKeys(function ($section) {
$name = $section->class_section_name ?? $section->section_name ?? $section->name ?? ''; $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(); ->all();
} }
+9
View File
@@ -0,0 +1,9 @@
<?php
namespace App\Services;
use App\Services\Assignment\AssignmentService as AssignmentDomainService;
class AssignmentService extends AssignmentDomainService
{
}
@@ -55,21 +55,17 @@ class AttendanceNotificationLogService
?string $schoolYear = null ?string $schoolYear = null
): void { ): void {
try { try {
$where = [ $existingQuery = $this->notificationModel->query()
'student_id' => $studentId, ->where('student_id', $studentId)
'code' => $code, ->where('code', $code)
'incident_date' => $ymd, ->whereDate('incident_date', $ymd)
'channel' => $channel, ->where('channel', $channel);
];
if (!empty($to)) { if (!empty($to)) {
$where['to_address'] = $to; $existingQuery->where('to_address', $to);
} }
$existing = $this->notificationModel->query() $existing = $existingQuery->orderByDesc('id')->first();
->where($where)
->orderByDesc('id')
->first();
if ($existing && !empty($existing->id)) { if ($existing && !empty($existing->id)) {
$existing->update([ $existing->update([
@@ -29,7 +29,7 @@ class AttendanceViolationStudentResolverService
} }
$classStudents = $classStudentsQuery $classStudents = $classStudentsQuery
->where('school_year', $schoolYear) ->where('student_class.school_year', $schoolYear)
->get() ->get()
->toArray(); ->toArray();
@@ -53,7 +53,7 @@ class AttendanceViolationStudentResolverService
} }
$classStudents = $classStudentsQuery $classStudents = $classStudentsQuery
->where('school_year', $schoolYear) ->where('student_class.school_year', $schoolYear)
->get() ->get()
->toArray(); ->toArray();
+1 -1
View File
@@ -25,8 +25,8 @@ class BadgeTextFormatter
{ {
$s = (string) $s; $s = (string) $s;
$s = str_replace(["\xC2\xA0", "\xA0"], ' ', $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('/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/u', '', $s) ?? $s;
$s = preg_replace('/\s+/u', ' ', $s) ?? $s;
return trim($s); return trim($s);
} }
@@ -6,7 +6,7 @@ class BroadcastEmailSenderOptionsService
{ {
public function listOptions(): array public function listOptions(): array
{ {
$json = env('MAIL_SENDERS', '{}'); $json = getenv('MAIL_SENDERS') ?: env('MAIL_SENDERS', '{}');
$arr = json_decode($json, true); $arr = json_decode($json, true);
if (!is_array($arr) || empty($arr)) { if (!is_array($arr) || empty($arr)) {
@@ -91,7 +91,7 @@ class ClassPreparationCalculatorService
$qty = $isLowerGrades ? 0 : $students; $qty = $isLowerGrades ? 0 : $students;
break; break;
case 'teacher chair': case 'teacher chair':
$qty = (int) $teacherCount; $qty = $teacherCount > 0 ? (int) $teacherCount : 1;
break; break;
case 'trash bin': case 'trash bin':
case 'white board': case 'white board':
@@ -114,13 +114,22 @@ class ClassPreparationCalculatorService
{ {
$schoolYear = $schoolYear ?: (string) Configuration::getConfig('school_year'); $schoolYear = $schoolYear ?: (string) Configuration::getConfig('school_year');
$row = DB::table('teacher_class') $baseQuery = DB::table('teacher_class')
->select('COUNT(*) AS cnt') ->select('COUNT(*) AS cnt')
->where('class_section_id', $classSectionId) ->where('class_section_id', $classSectionId)
->whereIn('position', ['main', 'ta']) ->whereIn('position', ['main', 'ta']);
->where('school_year', $schoolYear)
->first();
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;
} }
} }
@@ -11,7 +11,7 @@ class ClassPreparationInventoryService
$inventoryMap = array_fill_keys($allowed, 0); $inventoryMap = array_fill_keys($allowed, 0);
$joinRows = DB::table('inventory_items as ii') $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') ->join('inventory_categories as ic', 'ic.id', '=', 'ii.category_id')
->where('ii.type', 'classroom') ->where('ii.type', 'classroom')
->where('ii.school_year', $schoolYear) ->where('ii.school_year', $schoolYear)
@@ -31,7 +31,7 @@ class ClassPreparationInventoryService
} }
$nameRows = DB::table('inventory_items') $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('type', 'classroom')
->where('school_year', $schoolYear) ->where('school_year', $schoolYear)
->whereIn('name', $allowed); ->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; return $inventoryMap;
} }
} }
@@ -9,7 +9,7 @@ class ClassPreparationRosterService
public function getClassSectionStudentCounts(string $schoolYear, string $semester, bool $limitToSemester): array public function getClassSectionStudentCounts(string $schoolYear, string $semester, bool $limitToSemester): array
{ {
$query = DB::table('student_class as sc') $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') ->join('students as s', 's.id', '=', 'sc.student_id')
->where('s.is_active', 1) ->where('s.is_active', 1)
->where('sc.school_year', $schoolYear); ->where('sc.school_year', $schoolYear);
@@ -24,7 +24,7 @@ class ClassPreparationRosterService
public function getStudentCountForSection(string $schoolYear, string $semester, bool $limitToSemester, string $classSectionId): int public function getStudentCountForSection(string $schoolYear, string $semester, bool $limitToSemester, string $classSectionId): int
{ {
$query = DB::table('student_class as sc') $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') ->join('students as s', 's.id', '=', 'sc.student_id')
->where('s.is_active', 1) ->where('s.is_active', 1)
->where('sc.class_section_id', $classSectionId) ->where('sc.class_section_id', $classSectionId)
@@ -50,7 +50,7 @@ class CompetitionScoresSaveService
->where('competition_id', $competitionId) ->where('competition_id', $competitionId)
->where('class_section_id', $classSectionId) ->where('class_section_id', $classSectionId)
->get() ->get()
->map(fn ($row) => (array) $row) ->map(fn ($row) => $row->toArray())
->all(); ->all();
$map = []; $map = [];
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace App\Services;
use App\Services\Fees\FeeRefundCalculatorService;
class FeeCalculationService
{
public function __construct(
private FeeRefundCalculatorService $refundCalculator
) {
}
public function calculateRefund(array $students, int $parentId): float
{
$result = $this->refundCalculator->calculateRefund($students, $parentId);
return (float) ($result['refund_amount'] ?? 0);
}
}
+10 -6
View File
@@ -6,20 +6,24 @@ class FeeGradeService
{ {
public function normalizeGrade(?string $grade): string 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 public function compareGrades(string $gradeA, string $gradeB): int
{ {
$valA = $this->getGradeLevel($gradeA); $normA = $this->normalizeGrade($gradeA);
$valB = $this->getGradeLevel($gradeB); $normB = $this->normalizeGrade($gradeB);
$valA = $this->getGradeLevel($normA);
$valB = $this->getGradeLevel($normB);
if ($valA !== $valB) { if ($valA !== $valB) {
return $valA <=> $valB; return $valA <=> $valB;
} }
preg_match('/\d+([A-Z]*)$/i', strtoupper($gradeA), $suffixA); preg_match('/\d+([A-Z]*)$/i', $normA, $suffixA);
preg_match('/\d+([A-Z]*)$/i', strtoupper($gradeB), $suffixB); preg_match('/\d+([A-Z]*)$/i', $normB, $suffixB);
return strcmp($suffixA[1] ?? '', $suffixB[1] ?? ''); return strcmp($suffixA[1] ?? '', $suffixB[1] ?? '');
} }
@@ -35,7 +39,7 @@ class FeeGradeService
return 99; return 99;
} }
if (preg_match('/^(\d+)([A-Z]*)$/i', $grade, $matches)) { if (preg_match('/^(\d+)/', $grade, $matches)) {
return (int) $matches[1]; return (int) $matches[1];
} }
@@ -97,7 +97,7 @@ class FinancialSummaryService
$query = DB::table('additional_charges as ac') $query = DB::table('additional_charges as ac')
->selectRaw('COALESCE(SUM(ac.amount), 0) AS amount') ->selectRaw('COALESCE(SUM(ac.amount), 0) AS amount')
->where('ac.school_year', $schoolYear) ->where('ac.school_year', $schoolYear)
->where('ac.status !=', 'void'); ->where('ac.status', '!=', 'void');
if (!empty($dateFrom)) { if (!empty($dateFrom)) {
$query->whereRaw('DATE(ac.due_date) >= ?', [$dateFrom]); $query->whereRaw('DATE(ac.due_date) >= ?', [$dateFrom]);
@@ -115,11 +115,11 @@ class FinancialSummaryService
$query = DB::table('additional_charges as ac') $query = DB::table('additional_charges as ac')
->selectRaw('ac.parent_id, COALESCE(SUM(ac.amount), 0) AS amount') ->selectRaw('ac.parent_id, COALESCE(SUM(ac.amount), 0) AS amount')
->where('ac.school_year', $schoolYear) ->where('ac.school_year', $schoolYear)
->where('ac.status !=', 'void') ->where('ac.status', '!=', 'void')
->where(function ($q) { ->where(function ($q) {
$q->whereNull('ac.invoice_id') $q->whereNull('ac.invoice_id')
->orWhere('ac.invoice_id', 0) ->orWhere('ac.invoice_id', 0)
->orWhere('ac.status !=', 'applied'); ->orWhere('ac.status', '!=', 'applied');
}) })
->groupBy('ac.parent_id'); ->groupBy('ac.parent_id');
@@ -96,17 +96,14 @@ class FinancialUnpaidParentsService
} }
$extraRows = DB::table('additional_charges as ac') $extraRows = DB::table('additional_charges as ac')
->selectRaw( ->selectRaw("ac.parent_id, u.firstname, u.lastname, u.email, COALESCE(SUM(ac.amount), 0) AS total_extra")
"ac.parent_id, u.firstname, u.lastname, u.email, COALESCE(SUM(ac.amount), 0) AS total_extra",
false
)
->leftJoin('users as u', 'u.id', '=', 'ac.parent_id') ->leftJoin('users as u', 'u.id', '=', 'ac.parent_id')
->where('ac.school_year', $schoolYear) ->where('ac.school_year', $schoolYear)
->where('ac.status !=', 'void') ->where('ac.status', '!=', 'void')
->where(function ($q) { ->where(function ($q) {
$q->whereNull('ac.invoice_id') $q->whereNull('ac.invoice_id')
->orWhere('ac.invoice_id', 0) ->orWhere('ac.invoice_id', 0)
->orWhere('ac.status !=', 'applied'); ->orWhere('ac.status', '!=', 'applied');
}) })
->groupBy('ac.parent_id') ->groupBy('ac.parent_id')
->get() ->get()
@@ -167,7 +167,7 @@ class HomeworkTrackingService
->whereIn('tc.position', ['main', 'ta']) ->whereIn('tc.position', ['main', 'ta'])
->orderBy('cs.class_id', 'ASC') ->orderBy('cs.class_id', 'ASC')
->orderBy('cs.class_section_name', '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') ->orderBy('u.firstname', 'ASC')
->get(); ->get();
@@ -65,13 +65,13 @@ class IncidentAnalysisService
} }
if ($students[$studentKey]['last_incident'] === null) { 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'][] = [ $students[$studentKey]['logs'][] = [
'incident' => $incident['incident'] ?? '', 'incident' => $incident['incident'] ?? '',
'incident_state' => $state, 'incident_state' => $state,
'incident_datetime' => $incident['incident_datetime'] ?? null, 'incident_datetime' => $this->formatIncidentDate($incident['incident_datetime'] ?? null),
'open_description' => $incident['open_description'] ?? null, 'open_description' => $incident['open_description'] ?? null,
'close_description' => $incident['close_description'] ?? null, 'close_description' => $incident['close_description'] ?? null,
'cancel_description' => $incident['cancel_description'] ?? null, 'cancel_description' => $incident['cancel_description'] ?? null,
@@ -89,4 +89,23 @@ class IncidentAnalysisService
return $studentRows; 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;
}
} }
@@ -69,16 +69,20 @@ class IncidentLookupService
} }
$rows = DB::table('users') $rows = DB::table('users')
->select('id, firstname, lastname') ->select('id', 'firstname', 'lastname')
->whereIn('id', $userIds) ->whereIn('id', $userIds)
->get() ->get()
->map(fn ($row) => (array) $row) ->map(fn ($row) => $row instanceof \Illuminate\Contracts\Support\Arrayable ? $row->toArray() : (array) $row)
->all(); ->all();
$map = []; $map = [];
foreach ($rows as $row) { foreach ($rows as $row) {
$id = $row['id'] ?? $row['user_id'] ?? null;
if ($id === null) {
continue;
}
$name = trim((string) ($row['firstname'] ?? '') . ' ' . (string) ($row['lastname'] ?? '')); $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; return $map;
@@ -30,7 +30,7 @@ class InvoiceGenerationService
->where('parent_id', $parentId) ->where('parent_id', $parentId)
->where('school_year', $schoolYear) ->where('school_year', $schoolYear)
->get() ->get()
->map(fn ($r) => (array) $r) ->map(fn ($r) => $r->toArray())
->all(); ->all();
if (empty($enrollments)) { if (empty($enrollments)) {
@@ -40,7 +40,11 @@ class InvoiceManagementService
$parents = User::getUsersByRoleAndSchoolYear('parent', $schoolYear); $parents = User::getUsersByRoleAndSchoolYear('parent', $schoolYear);
foreach ($parents as $parent) { 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 = [ $parentData = [
'parent_name' => trim(($parent['firstname'] ?? '') . ' ' . ($parent['lastname'] ?? '')), 'parent_name' => trim(($parent['firstname'] ?? '') . ' ' . ($parent['lastname'] ?? '')),
@@ -106,7 +110,7 @@ class InvoiceManagementService
->where('student_id', $student['id']) ->where('student_id', $student['id'])
->where('school_year', $schoolYear) ->where('school_year', $schoolYear)
->get() ->get()
->map(fn ($r) => (array) $r) ->map(fn ($r) => $r->toArray())
->all(); ->all();
foreach ($enrollments as $enrollment) { foreach ($enrollments as $enrollment) {
+5
View File
@@ -94,6 +94,10 @@ class ExamScoreService
if (!is_numeric($studentId)) { if (!is_numeric($studentId)) {
continue; continue;
} }
$student = Student::query()->find((int) $studentId);
if (!$student) {
continue;
}
$rawScore = $data['score'] ?? null; $rawScore = $data['score'] ?? null;
$normalizedScore = is_numeric($rawScore) ? (float) $rawScore : null; $normalizedScore = is_numeric($rawScore) ? (float) $rawScore : null;
@@ -106,6 +110,7 @@ class ExamScoreService
$payload = [ $payload = [
'student_id' => (int) $studentId, 'student_id' => (int) $studentId,
'school_id' => $student->school_id,
'class_section_id' => $classSectionId, 'class_section_id' => $classSectionId,
'updated_by' => $updatedBy, 'updated_by' => $updatedBy,
'score' => $normalizedScore, 'score' => $normalizedScore,
+8 -2
View File
@@ -57,12 +57,13 @@ class HomeworkScoreService
->whereIn('semester', $semVariants) ->whereIn('semester', $semVariants)
->where('school_year', $schoolYear) ->where('school_year', $schoolYear)
->get() ->get()
->map(fn ($row) => (array) $row) ->map(fn ($row) => $row->toArray())
->all(); ->all();
$scoresMap = []; $scoresMap = [];
foreach ($scoresRows as $row) { 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) { foreach ($students as &$student) {
@@ -196,6 +197,10 @@ class HomeworkScoreService
->get(); ->get();
foreach ($students as $student) { foreach ($students as $student) {
$studentRow = Student::query()->find((int) $student->student_id);
if (!$studentRow) {
continue;
}
$exists = Homework::query() $exists = Homework::query()
->where('student_id', $student->student_id) ->where('student_id', $student->student_id)
->where('homework_index', $nextIndex) ->where('homework_index', $nextIndex)
@@ -210,6 +215,7 @@ class HomeworkScoreService
Homework::query()->create([ Homework::query()->create([
'student_id' => (int) $student->student_id, 'student_id' => (int) $student->student_id,
'school_id' => $studentRow->school_id,
'class_section_id' => $classSectionId, 'class_section_id' => $classSectionId,
'updated_by' => $updatedBy, 'updated_by' => $updatedBy,
'homework_index' => $nextIndex, 'homework_index' => $nextIndex,
@@ -50,12 +50,14 @@ class ParticipationScoreService
$scoreMap = []; $scoreMap = [];
foreach ($scoresRows as $row) { 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) { foreach ($students as &$student) {
$score = $scoreMap[$student['student_id']] ?? '';
$student['scores'] = [ $student['scores'] = [
'score' => $scoreMap[$student['student_id']] ?? '', 'score' => $score,
]; ];
} }
unset($student); unset($student);
+8 -2
View File
@@ -57,12 +57,13 @@ class ProjectScoreService
->where('semester', $semester) ->where('semester', $semester)
->where('school_year', $schoolYear) ->where('school_year', $schoolYear)
->get() ->get()
->map(fn ($row) => (array) $row) ->map(fn ($row) => $row->toArray())
->all(); ->all();
$scoresMap = []; $scoresMap = [];
foreach ($scoresRows as $row) { 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) { foreach ($students as &$student) {
@@ -193,6 +194,10 @@ class ProjectScoreService
->get(); ->get();
foreach ($students as $student) { foreach ($students as $student) {
$studentRow = Student::query()->find((int) $student->student_id);
if (!$studentRow) {
continue;
}
$exists = Project::query() $exists = Project::query()
->where('student_id', $student->student_id) ->where('student_id', $student->student_id)
->where('project_index', $nextIndex) ->where('project_index', $nextIndex)
@@ -207,6 +212,7 @@ class ProjectScoreService
Project::query()->create([ Project::query()->create([
'student_id' => (int) $student->student_id, 'student_id' => (int) $student->student_id,
'school_id' => $studentRow->school_id,
'class_section_id' => $classSectionId, 'class_section_id' => $classSectionId,
'updated_by' => $updatedBy, 'updated_by' => $updatedBy,
'project_index' => $nextIndex, 'project_index' => $nextIndex,
+13 -2
View File
@@ -56,12 +56,13 @@ class QuizScoreService
->where('semester', $semester) ->where('semester', $semester)
->where('school_year', $schoolYear) ->where('school_year', $schoolYear)
->get() ->get()
->map(fn ($row) => (array) $row) ->map(fn ($row) => $row->toArray())
->all(); ->all();
$scoresMap = []; $scoresMap = [];
foreach ($scoresRows as $row) { 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) { foreach ($students as &$student) {
@@ -135,6 +136,10 @@ class QuizScoreService
if (!is_array($quizData)) { if (!is_array($quizData)) {
$quizData = [1 => $quizData]; $quizData = [1 => $quizData];
} }
$student = Student::query()->find((int) $studentId);
if (!$student) {
continue;
}
foreach ($quizData as $quizNumber => $score) { foreach ($quizData as $quizNumber => $score) {
if (!is_numeric($quizNumber)) { if (!is_numeric($quizNumber)) {
continue; continue;
@@ -152,6 +157,7 @@ class QuizScoreService
$payload = [ $payload = [
'student_id' => (int) $studentId, 'student_id' => (int) $studentId,
'school_id' => $student->school_id,
'class_section_id' => $classSectionId, 'class_section_id' => $classSectionId,
'updated_by' => $updatedBy, 'updated_by' => $updatedBy,
'quiz_index' => (int) $quizNumber, 'quiz_index' => (int) $quizNumber,
@@ -190,6 +196,10 @@ class QuizScoreService
->get(); ->get();
foreach ($students as $student) { foreach ($students as $student) {
$studentRow = Student::query()->find((int) $student->student_id);
if (!$studentRow) {
continue;
}
$exists = Quiz::query() $exists = Quiz::query()
->where('student_id', $student->student_id) ->where('student_id', $student->student_id)
->where('quiz_index', $nextIndex) ->where('quiz_index', $nextIndex)
@@ -204,6 +214,7 @@ class QuizScoreService
Quiz::query()->create([ Quiz::query()->create([
'student_id' => (int) $student->student_id, 'student_id' => (int) $student->student_id,
'school_id' => $studentRow->school_id,
'class_section_id' => $classSectionId, 'class_section_id' => $classSectionId,
'updated_by' => $updatedBy, 'updated_by' => $updatedBy,
'quiz_index' => $nextIndex, 'quiz_index' => $nextIndex,
+17 -8
View File
@@ -111,14 +111,14 @@ class ScoreDashboardService
$sid = $student['student_id']; $sid = $student['student_id'];
$scoreRow = $scores[$sid] ?? null; $scoreRow = $scores[$sid] ?? null;
$student['scores'] = $scoreRow ? [ $student['scores'] = $scoreRow ? [
'homework' => $scoreRow->homework_avg, 'homework' => $this->normalizeScore($scoreRow->homework_avg),
'quiz' => $scoreRow->quiz_avg, 'quiz' => $this->normalizeScore($scoreRow->quiz_avg),
'project' => $scoreRow->project_avg, 'project' => $this->normalizeScore($scoreRow->project_avg),
'midterm_exam' => $scoreRow->midterm_exam_score, 'midterm_exam' => $this->normalizeScore($scoreRow->midterm_exam_score),
'final_exam' => $scoreRow->final_exam_score, 'final_exam' => $this->normalizeScore($scoreRow->final_exam_score),
'attendance' => $scoreRow->attendance_score, 'attendance' => $this->normalizeScore($scoreRow->attendance_score),
'ptap' => $scoreRow->ptap_score, 'ptap' => $this->normalizeScore($scoreRow->ptap_score),
'semester' => $scoreRow->semester_score, 'semester' => $this->normalizeScore($scoreRow->semester_score),
] : []; ] : [];
$student['comments'] = $commentMap[$sid] ?? []; $student['comments'] = $commentMap[$sid] ?? [];
} }
@@ -267,4 +267,13 @@ class ScoreDashboardService
'selectedYear' => $schoolYear, 'selectedYear' => $schoolYear,
]; ];
} }
private function normalizeScore(mixed $value): mixed
{
if ($value === null || $value === '') {
return $value;
}
return is_numeric($value) ? (float) $value : $value;
}
} }
+1 -1
View File
@@ -73,7 +73,7 @@ class UserManagementService
try { try {
DB::transaction(function () use ($userId, $user) { DB::transaction(function () use ($userId, $user) {
UserRole::query()->where('user_id', $userId)->delete(); UserRole::query()->where('user_id', $userId)->forceDelete();
$user->delete(); $user->delete();
}); });
} catch (\Throwable $e) { } catch (\Throwable $e) {

Some files were not shown because too many files have changed in this diff Show More