add more controller
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Administrator;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\EmergencyContacts\EmergencyContactUpdateRequest;
|
||||
use App\Http\Resources\EmergencyContacts\EmergencyContactGroupResource;
|
||||
use App\Http\Resources\EmergencyContacts\EmergencyContactResource;
|
||||
use App\Services\EmergencyContacts\EmergencyContactCrudService;
|
||||
use App\Services\EmergencyContacts\EmergencyContactDirectoryService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class EmergencyContactController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private EmergencyContactDirectoryService $directoryService,
|
||||
private EmergencyContactCrudService $crudService
|
||||
) {
|
||||
}
|
||||
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$groups = $this->directoryService->groups();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'groups' => EmergencyContactGroupResource::collection($groups),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(EmergencyContactUpdateRequest $request, int $contactId): JsonResponse
|
||||
{
|
||||
$contact = $this->crudService->update($contactId, $request->validated());
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'contact' => new EmergencyContactResource($contact),
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(int $contactId): JsonResponse
|
||||
{
|
||||
$this->crudService->delete($contactId);
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Administrator;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Teachers\TeacherAssignmentDeleteRequest;
|
||||
use App\Http\Requests\Teachers\TeacherAssignmentListRequest;
|
||||
use App\Http\Requests\Teachers\TeacherAssignmentStoreRequest;
|
||||
use App\Http\Resources\Teachers\TeacherAssignmentResource;
|
||||
use App\Http\Resources\Teachers\TeacherClassResource;
|
||||
use App\Services\Teachers\TeacherAssignmentService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class TeacherClassAssignmentController extends BaseApiController
|
||||
{
|
||||
public function __construct(private TeacherAssignmentService $assignmentService)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index(TeacherAssignmentListRequest $request): JsonResponse
|
||||
{
|
||||
$data = $this->assignmentService->listAssignments($request->validated()['school_year'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'school_year' => $data['school_year'],
|
||||
'teachers' => TeacherAssignmentResource::collection($data['teachers']),
|
||||
'classes' => TeacherClassResource::collection($data['classes']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(TeacherAssignmentStoreRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$payload['updated_by'] = (int) (auth()->id() ?? 0);
|
||||
|
||||
$result = $this->assignmentService->assign($payload);
|
||||
$status = $result['ok'] ? 200 : 422;
|
||||
|
||||
return response()->json([
|
||||
'ok' => (bool) $result['ok'],
|
||||
'message' => $result['message'] ?? '',
|
||||
'position' => $result['position'] ?? null,
|
||||
], $status);
|
||||
}
|
||||
|
||||
public function destroy(TeacherAssignmentDeleteRequest $request): JsonResponse
|
||||
{
|
||||
$result = $this->assignmentService->delete($request->validated());
|
||||
$status = $result['ok'] ? 200 : 422;
|
||||
|
||||
return response()->json([
|
||||
'ok' => (bool) $result['ok'],
|
||||
'message' => $result['message'] ?? '',
|
||||
], $status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Exams;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Exams\ExamDraftAdminLegacyRequest;
|
||||
use App\Http\Requests\Exams\ExamDraftAdminReviewRequest;
|
||||
use App\Http\Requests\Exams\ExamDraftTeacherStoreRequest;
|
||||
use App\Http\Resources\Exams\ExamDraftResource;
|
||||
use App\Services\Exams\ExamDraftAdminService;
|
||||
use App\Services\Exams\ExamDraftTeacherService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class ExamDraftController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private ExamDraftTeacherService $teacherService,
|
||||
private ExamDraftAdminService $adminService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function teacherIndex(): JsonResponse
|
||||
{
|
||||
$teacherId = (int) (auth()->id() ?? 0);
|
||||
if ($teacherId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$data = $this->teacherService->listForTeacher($teacherId);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'assignments' => $data['assignments'],
|
||||
'drafts' => ExamDraftResource::collection($data['drafts']),
|
||||
'legacy_exams' => ExamDraftResource::collection($data['legacyExams']),
|
||||
'exam_types' => $data['examTypes'],
|
||||
'status_options' => $data['statusOptions'],
|
||||
'school_year' => $data['schoolYear'],
|
||||
'semester' => $data['semester'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function teacherStore(ExamDraftTeacherStoreRequest $request): JsonResponse
|
||||
{
|
||||
$teacherId = (int) (auth()->id() ?? 0);
|
||||
if ($teacherId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$result = $this->teacherService->store($teacherId, $request->validated(), $request->file('draft_file'));
|
||||
if (!$result['ok']) {
|
||||
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Unable to save.'], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'draft' => new ExamDraftResource($result['draft']),
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function adminIndex(): JsonResponse
|
||||
{
|
||||
$data = $this->adminService->list();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'drafts' => ExamDraftResource::collection($data['drafts']),
|
||||
'class_sections' => $data['classSections'],
|
||||
'legacy_by_class' => $data['legacyByClass'],
|
||||
'exam_types' => $data['examTypes'],
|
||||
'status_options' => $data['statusOptions'],
|
||||
'school_year' => $data['schoolYear'],
|
||||
'semester' => $data['semester'],
|
||||
'allowed_extensions' => $data['allowedExtensions'],
|
||||
'max_upload_bytes' => $data['maxUploadBytes'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function adminUploadLegacy(ExamDraftAdminLegacyRequest $request): JsonResponse
|
||||
{
|
||||
$adminId = (int) (auth()->id() ?? 0);
|
||||
if ($adminId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$result = $this->adminService->uploadLegacy($adminId, $request->validated(), $request->file('old_exam_file'));
|
||||
if (!$result['ok']) {
|
||||
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Unable to save.'], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'draft' => new ExamDraftResource($result['draft']),
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function adminReview(ExamDraftAdminReviewRequest $request): JsonResponse
|
||||
{
|
||||
$adminId = (int) (auth()->id() ?? 0);
|
||||
if ($adminId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$result = $this->adminService->review($adminId, $request->validated(), $request->file('final_file'));
|
||||
if (!$result['ok']) {
|
||||
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Unable to save.'], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'draft' => new ExamDraftResource($result['draft']),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Parents;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Parents\ParentAttendanceReportSubmitRequest;
|
||||
use App\Http\Requests\Parents\ParentAttendanceReportListRequest;
|
||||
use App\Http\Requests\Parents\ParentAttendanceReportCheckRequest;
|
||||
use App\Http\Requests\Parents\ParentAttendanceReportUpdateRequest;
|
||||
use App\Http\Resources\Parents\ParentAttendanceReportResource;
|
||||
use App\Services\Parents\ParentAttendanceReportService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class ParentAttendanceReportController extends BaseApiController
|
||||
{
|
||||
public function __construct(private ParentAttendanceReportService $reportService)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function form(): JsonResponse
|
||||
{
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
if ($parentId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$data = $this->reportService->formData($parentId);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'students' => $data['students'],
|
||||
'today' => $data['today'],
|
||||
'sundays' => $data['sundays'],
|
||||
'defaultDate' => $data['defaultDate'],
|
||||
'myReports' => ParentAttendanceReportResource::collection($data['myReports']),
|
||||
'cutoffThreshold' => $data['cutoffThreshold'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function submit(ParentAttendanceReportSubmitRequest $request): JsonResponse
|
||||
{
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
if ($parentId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->reportService->submit($parentId, $request->validated());
|
||||
} catch (\RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'inserted' => $result['inserted'],
|
||||
'blocked' => $result['blocked'],
|
||||
'students' => $result['students'],
|
||||
'dates' => $result['dates'],
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function list(ParentAttendanceReportListRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$start = $payload['start'] ?? now()->toDateString();
|
||||
$end = $payload['end'] ?? null;
|
||||
$schoolYear = $payload['school_year'] ?? null;
|
||||
$semester = $payload['semester'] ?? null;
|
||||
|
||||
$rows = $this->reportService->listReports($start, $end, $schoolYear, $semester);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'reports' => ParentAttendanceReportResource::collection($rows),
|
||||
]);
|
||||
}
|
||||
|
||||
public function checkExisting(ParentAttendanceReportCheckRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$rows = $this->reportService->checkExisting($request->validated());
|
||||
} catch (\RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'students' => $rows,
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(ParentAttendanceReportUpdateRequest $request, int $reportId): JsonResponse
|
||||
{
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
if ($parentId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->reportService->updateReport($parentId, $reportId, $request->validated());
|
||||
} catch (\RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Parents;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Parents\ParentAttendanceRequest;
|
||||
use App\Http\Requests\Parents\ParentEnrollmentActionRequest;
|
||||
use App\Http\Requests\Parents\ParentEnrollmentRequest;
|
||||
use App\Http\Requests\Parents\ParentEmergencyContactRequest;
|
||||
use App\Http\Requests\Parents\ParentEventParticipationRequest;
|
||||
use App\Http\Requests\Parents\ParentInvoiceRequest;
|
||||
use App\Http\Requests\Parents\ParentProfileUpdateRequest;
|
||||
use App\Http\Requests\Parents\ParentRegistrationRequest;
|
||||
use App\Http\Requests\Parents\ParentStudentUpdateRequest;
|
||||
use App\Http\Resources\Parents\ParentAttendanceResource;
|
||||
use App\Http\Resources\Parents\ParentEmergencyContactResource;
|
||||
use App\Http\Resources\Parents\ParentStudentResource;
|
||||
use App\Http\Resources\Invoices\InvoiceResource;
|
||||
use App\Services\Parents\ParentAttendanceService;
|
||||
use App\Services\Parents\ParentEmergencyContactService;
|
||||
use App\Services\Parents\ParentEnrollmentService;
|
||||
use App\Services\Parents\ParentEventParticipationService;
|
||||
use App\Services\Parents\ParentInvoiceService;
|
||||
use App\Services\Parents\ParentProfileService;
|
||||
use App\Services\Parents\ParentRegistrationService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class ParentController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private ParentAttendanceService $attendanceService,
|
||||
private ParentInvoiceService $invoiceService,
|
||||
private ParentEnrollmentService $enrollmentService,
|
||||
private ParentRegistrationService $registrationService,
|
||||
private ParentEmergencyContactService $emergencyContactService,
|
||||
private ParentProfileService $profileService,
|
||||
private ParentEventParticipationService $eventService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function attendance(ParentAttendanceRequest $request): JsonResponse
|
||||
{
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
if ($parentId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$data = $this->attendanceService->listAttendance($parentId, $request->validated()['school_year'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'attendance' => ParentAttendanceResource::collection($data['attendance']),
|
||||
'schoolYears' => $data['schoolYears'],
|
||||
'selectedYear' => $data['selectedYear'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function invoices(ParentInvoiceRequest $request): JsonResponse
|
||||
{
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
if ($parentId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$schoolYear = $request->validated()['school_year'] ?? null;
|
||||
$rows = $this->invoiceService->listInvoices($parentId, $schoolYear);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'invoices' => InvoiceResource::collection($rows),
|
||||
]);
|
||||
}
|
||||
|
||||
public function enrollments(ParentEnrollmentRequest $request): JsonResponse
|
||||
{
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
if ($parentId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$data = $this->enrollmentService->overview($parentId, $request->validated()['school_year'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'students' => ParentStudentResource::collection($data['students']),
|
||||
'schoolYears' => $data['schoolYears'],
|
||||
'selectedYear' => $data['selectedYear'],
|
||||
'withdrawalDeadline' => $data['withdrawalDeadline'],
|
||||
'lastDayOfRegistration' => $data['lastDayOfRegistration'],
|
||||
'schoolStartDate' => $data['schoolStartDate'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateEnrollments(ParentEnrollmentActionRequest $request): JsonResponse
|
||||
{
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
if ($parentId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$payload = $request->validated();
|
||||
$result = $this->enrollmentService->updateEnrollment(
|
||||
$parentId,
|
||||
$payload['enroll'] ?? [],
|
||||
$payload['withdraw'] ?? []
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'enrolled' => $result['enrolled'],
|
||||
'withdrawn' => $result['withdrawn'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function registration(): JsonResponse
|
||||
{
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
if ($parentId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$data = $this->registrationService->overview($parentId);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'parent' => $data['parent'],
|
||||
'existingKids' => ParentStudentResource::collection($data['existingKids']),
|
||||
'emergencies' => ParentEmergencyContactResource::collection($data['emergencies']),
|
||||
'maxChilds' => $data['maxChilds'],
|
||||
'maxEmergency' => $data['maxEmergency'],
|
||||
'lastDayOfRegistration' => $data['lastDayOfRegistration'],
|
||||
'registrationAgeDeadline' => $data['registrationAgeDeadline'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function storeRegistration(ParentRegistrationRequest $request): JsonResponse
|
||||
{
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
if ($parentId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$payload = $request->validated();
|
||||
$result = $this->registrationService->register(
|
||||
$parentId,
|
||||
$payload['students'] ?? [],
|
||||
$payload['emergency_contacts'] ?? []
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'created_student_ids' => $result['created_student_ids'],
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function updateStudent(ParentStudentUpdateRequest $request, int $studentId): JsonResponse
|
||||
{
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
if ($parentId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$this->registrationService->updateStudent($parentId, $studentId, $request->validated());
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function deleteStudent(int $studentId): JsonResponse
|
||||
{
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
if ($parentId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$this->registrationService->deleteStudent($parentId, $studentId);
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function emergencyContacts(): JsonResponse
|
||||
{
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
if ($parentId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$rows = $this->emergencyContactService->list($parentId);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'contacts' => ParentEmergencyContactResource::collection($rows),
|
||||
]);
|
||||
}
|
||||
|
||||
public function storeEmergencyContact(ParentEmergencyContactRequest $request): JsonResponse
|
||||
{
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
if ($parentId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$contact = $this->emergencyContactService->store($parentId, $request->validated());
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'contact' => new ParentEmergencyContactResource($contact),
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function updateEmergencyContact(ParentEmergencyContactRequest $request, int $contactId): JsonResponse
|
||||
{
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
if ($parentId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$contact = $this->emergencyContactService->update($parentId, $contactId, $request->validated());
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'contact' => new ParentEmergencyContactResource($contact),
|
||||
]);
|
||||
}
|
||||
|
||||
public function profile(): JsonResponse
|
||||
{
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
if ($parentId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$profile = $this->profileService->getProfile($parentId);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'profile' => $profile,
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateProfile(ParentProfileUpdateRequest $request): JsonResponse
|
||||
{
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
if ($parentId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$profile = $this->profileService->updateProfile($parentId, $request->validated());
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'profile' => $profile,
|
||||
]);
|
||||
}
|
||||
|
||||
public function events(): JsonResponse
|
||||
{
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
if ($parentId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$data = $this->eventService->overview($parentId);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'activeEvents' => $data['activeEvents'],
|
||||
'charges' => $data['charges'],
|
||||
'yourStudents' => $data['yourStudents'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateParticipation(ParentEventParticipationRequest $request): JsonResponse
|
||||
{
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
if ($parentId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$this->eventService->updateParticipation($parentId, $request->validated()['participation'] ?? []);
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Reports;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Reports\SlipLogListRequest;
|
||||
use App\Http\Requests\Reports\SlipPreviewRequest;
|
||||
use App\Http\Requests\Reports\SlipPrintRequest;
|
||||
use App\Http\Requests\Reports\SlipReprintRequest;
|
||||
use App\Http\Resources\Reports\LateSlipLogResource;
|
||||
use App\Services\Reports\SlipPrinterService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class SlipPrinterController extends BaseApiController
|
||||
{
|
||||
public function __construct(private SlipPrinterService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function print(SlipPrintRequest $request)
|
||||
{
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
$result = $this->service->print($request->validated(), $userId);
|
||||
|
||||
if (!$result['ok']) {
|
||||
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Print failed.'], 422);
|
||||
}
|
||||
|
||||
return response($result['pdf'], 200, [
|
||||
'Content-Type' => 'application/pdf',
|
||||
'Content-Disposition' => 'inline; filename="' . $result['filename'] . '"',
|
||||
]);
|
||||
}
|
||||
|
||||
public function preview(SlipPreviewRequest $request): JsonResponse
|
||||
{
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
$result = $this->service->preview($request->validated(), $userId);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'text' => $result['text'],
|
||||
'width' => $result['width'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function logs(SlipLogListRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$rows = $this->service->logs($payload['school_year'] ?? null, $payload['semester'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'logs' => LateSlipLogResource::collection($rows),
|
||||
]);
|
||||
}
|
||||
|
||||
public function reprint(SlipReprintRequest $request)
|
||||
{
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
$result = $this->service->reprint((int) $request->validated()['id'], $userId);
|
||||
|
||||
if (!$result['ok']) {
|
||||
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Slip not found.'], 404);
|
||||
}
|
||||
|
||||
return response($result['pdf'], 200, [
|
||||
'Content-Type' => 'application/pdf',
|
||||
'Content-Disposition' => 'inline; filename="' . $result['filename'] . '"',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Settings;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Settings\ConfigurationStoreRequest;
|
||||
use App\Http\Requests\Settings\ConfigurationUpdateRequest;
|
||||
use App\Http\Resources\Settings\ConfigurationResource;
|
||||
use App\Services\Settings\ConfigurationService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class ConfigurationAdminController extends BaseApiController
|
||||
{
|
||||
public function __construct(private ConfigurationService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$configs = $this->service->list();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'configs' => ConfigurationResource::collection($configs),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(ConfigurationStoreRequest $request): JsonResponse
|
||||
{
|
||||
$config = $this->service->store($request->validated());
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'config' => new ConfigurationResource($config->toArray()),
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function update(ConfigurationUpdateRequest $request, int $id): JsonResponse
|
||||
{
|
||||
$config = $this->service->update($id, $request->validated());
|
||||
if (!$config) {
|
||||
return response()->json(['ok' => false, 'message' => 'Configuration not found.'], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'config' => new ConfigurationResource($config->toArray()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(int $id): JsonResponse
|
||||
{
|
||||
$deleted = $this->service->delete($id);
|
||||
if (!$deleted) {
|
||||
return response()->json(['ok' => false, 'message' => 'Configuration not found.'], 404);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Staff;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Teachers\TeacherAbsenceSubmitRequest;
|
||||
use App\Http\Requests\Teachers\TeacherClassViewRequest;
|
||||
use App\Http\Requests\Teachers\TeacherSwitchClassRequest;
|
||||
use App\Http\Resources\Teachers\TeacherAbsenceResource;
|
||||
use App\Http\Resources\Teachers\TeacherClassResource;
|
||||
use App\Http\Resources\Teachers\TeacherStudentResource;
|
||||
use App\Models\User;
|
||||
use App\Services\Teachers\TeacherAbsenceService;
|
||||
use App\Services\Teachers\TeacherDashboardService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class TeacherController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private TeacherDashboardService $dashboardService,
|
||||
private TeacherAbsenceService $absenceService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function classes(TeacherClassViewRequest $request): JsonResponse
|
||||
{
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
if ($userId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $this->dashboardService->classView(
|
||||
$userId,
|
||||
$request->validated()['class_section_id'] ?? null
|
||||
);
|
||||
} catch (\RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 403);
|
||||
}
|
||||
|
||||
$teacher = $this->teacherPayload($data['teacher'] ?? null, $userId);
|
||||
$studentsBySection = [];
|
||||
|
||||
foreach (($data['studentsBySection'] ?? []) as $sectionId => $students) {
|
||||
$studentsBySection[(string) $sectionId] = TeacherStudentResource::collection($students);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'teacher' => $teacher,
|
||||
'classes' => TeacherClassResource::collection($data['classes'] ?? []),
|
||||
'students' => TeacherStudentResource::collection($data['students'] ?? []),
|
||||
'students_by_section' => $studentsBySection,
|
||||
'assigned_names' => $data['assignedNames'] ?? [],
|
||||
'active_class_section_id' => $data['active_class_section_id'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function switchClass(TeacherSwitchClassRequest $request): JsonResponse
|
||||
{
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
if ($userId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$requested = (int) $request->validated()['class_section_id'];
|
||||
|
||||
try {
|
||||
$data = $this->dashboardService->classView($userId, $requested);
|
||||
} catch (\RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 403);
|
||||
}
|
||||
|
||||
if (($data['active_class_section_id'] ?? null) !== $requested) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'You are not assigned to that class.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'active_class_section_id' => $requested,
|
||||
]);
|
||||
}
|
||||
|
||||
public function absenceForm(): JsonResponse
|
||||
{
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
if ($userId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$data = $this->absenceService->formData($userId);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'teacher_name' => $data['teacher_name'],
|
||||
'semester' => $data['semester'],
|
||||
'school_year' => $data['school_year'],
|
||||
'existing' => TeacherAbsenceResource::collection($data['existing'] ?? []),
|
||||
'available_dates' => $data['available_dates'] ?? [],
|
||||
]);
|
||||
}
|
||||
|
||||
public function submitAbsence(TeacherAbsenceSubmitRequest $request): JsonResponse
|
||||
{
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
if ($userId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$result = $this->absenceService->submit($userId, $request->validated());
|
||||
|
||||
return response()->json([
|
||||
'ok' => (bool) ($result['ok'] ?? false),
|
||||
'message' => $result['message'] ?? '',
|
||||
'saved' => $result['saved'] ?? 0,
|
||||
'dates' => $result['dates'] ?? [],
|
||||
], (int) ($result['status'] ?? 200));
|
||||
}
|
||||
|
||||
private function teacherPayload(?array $teacher, int $userId): ?array
|
||||
{
|
||||
if (!$teacher) {
|
||||
$model = User::query()->find($userId);
|
||||
$teacher = $model?->toArray();
|
||||
}
|
||||
|
||||
if (!$teacher) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => (int) ($teacher['id'] ?? 0),
|
||||
'firstname' => (string) ($teacher['firstname'] ?? ''),
|
||||
'lastname' => (string) ($teacher['lastname'] ?? ''),
|
||||
'email' => $teacher['email'] ?? null,
|
||||
'role' => User::getUserRoleName($userId),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Students;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Students\StudentAssignClassRequest;
|
||||
use App\Http\Requests\Students\StudentAssignmentListRequest;
|
||||
use App\Http\Requests\Students\StudentAutoDistributeRequest;
|
||||
use App\Http\Requests\Students\StudentPromotionTotalsRequest;
|
||||
use App\Http\Requests\Students\StudentRemoveClassRequest;
|
||||
use App\Http\Requests\Students\StudentSetActiveRequest;
|
||||
use App\Http\Requests\Students\StudentUpdateRequest;
|
||||
use App\Http\Resources\Students\StudentAssignmentResource;
|
||||
use App\Http\Resources\Students\StudentClassSectionResource;
|
||||
use App\Http\Resources\Students\StudentRemovedResource;
|
||||
use App\Http\Resources\Students\StudentScoreCardRowResource;
|
||||
use App\Services\Students\StudentAssignmentService;
|
||||
use App\Services\Students\StudentAutoDistributionService;
|
||||
use App\Services\Students\StudentDirectoryService;
|
||||
use App\Services\Students\StudentProfileService;
|
||||
use App\Services\Students\StudentScoreCardService;
|
||||
use App\Services\Students\StudentStatusService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class StudentController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private StudentAssignmentService $assignmentService,
|
||||
private StudentDirectoryService $directoryService,
|
||||
private StudentStatusService $statusService,
|
||||
private StudentAutoDistributionService $autoDistributionService,
|
||||
private StudentProfileService $profileService,
|
||||
private StudentScoreCardService $scoreCardService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function assignments(StudentAssignmentListRequest $request): JsonResponse
|
||||
{
|
||||
$data = $this->directoryService->assignmentData($request->validated()['school_year'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'students' => StudentAssignmentResource::collection($data['students']),
|
||||
'classes' => StudentClassSectionResource::collection($data['classes']),
|
||||
'schoolYears' => $data['schoolYears'],
|
||||
'selectedYear' => $data['selectedYear'],
|
||||
'currentYear' => $data['currentYear'],
|
||||
'isCurrentYear' => $data['isCurrentYear'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function assignClass(StudentAssignClassRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$result = $this->assignmentService->assignClasses(
|
||||
(int) $payload['student_id'],
|
||||
$payload['class_section_id'],
|
||||
(bool) ($payload['is_event_only'] ?? false),
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
$status = $result['ok'] ? 200 : 422;
|
||||
|
||||
return response()->json($result + ['ok' => (bool) $result['ok']], $status);
|
||||
}
|
||||
|
||||
public function removeClass(StudentRemoveClassRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$result = $this->assignmentService->removeClass(
|
||||
(int) $payload['student_id'],
|
||||
(int) $payload['class_section_id'],
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
$status = $result['ok'] ? 200 : 422;
|
||||
|
||||
return response()->json($result + ['ok' => (bool) $result['ok']], $status);
|
||||
}
|
||||
|
||||
public function removed(StudentAssignmentListRequest $request): JsonResponse
|
||||
{
|
||||
$data = $this->directoryService->removedStudents($request->validated()['school_year'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'active_students' => StudentRemovedResource::collection($data['active_students']),
|
||||
'removed_students' => StudentRemovedResource::collection($data['removed_students']),
|
||||
'school_year' => $data['school_year'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function setActive(StudentSetActiveRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$result = $this->statusService->setActive(
|
||||
(int) $payload['student_id'],
|
||||
(bool) $payload['is_active'],
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
$status = $result['ok'] ? 200 : 422;
|
||||
|
||||
return response()->json($result + ['ok' => (bool) $result['ok']], $status);
|
||||
}
|
||||
|
||||
public function autoDistribute(StudentAutoDistributeRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$result = $this->autoDistributionService->autoDistribute(
|
||||
(int) ($payload['class_id'] ?? 0),
|
||||
(int) $payload['students_per_section'],
|
||||
$payload['school_year'] ?? null,
|
||||
(int) ($payload['class_section_id'] ?? 0),
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
$status = $result['ok'] ? 200 : 422;
|
||||
|
||||
return response()->json($result + ['ok' => (bool) $result['ok']], $status);
|
||||
}
|
||||
|
||||
public function promotionTotals(StudentPromotionTotalsRequest $request): JsonResponse
|
||||
{
|
||||
$data = $this->autoDistributionService->promotionTotals($request->validated()['school_year'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'year' => $data['year'],
|
||||
'rows' => $data['rows'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(StudentUpdateRequest $request, int $studentId): JsonResponse
|
||||
{
|
||||
$result = $this->profileService->updateStudent($studentId, $request->validated());
|
||||
$status = $result['ok'] ? 200 : 422;
|
||||
|
||||
return response()->json($result + ['ok' => (bool) $result['ok']], $status);
|
||||
}
|
||||
|
||||
public function scoreCard(int $studentId): JsonResponse
|
||||
{
|
||||
$result = $this->scoreCardService->scoreCard($studentId);
|
||||
if (!$result['ok']) {
|
||||
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Not found.'], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'student' => $result['student'],
|
||||
'rows' => StudentScoreCardRowResource::collection($result['rows']),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Subjects;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Subjects\SubjectCurriculumStoreRequest;
|
||||
use App\Http\Requests\Subjects\SubjectCurriculumUpdateRequest;
|
||||
use App\Http\Resources\Subjects\SubjectClassResource;
|
||||
use App\Http\Resources\Subjects\SubjectCurriculumResource;
|
||||
use App\Models\SubjectCurriculum;
|
||||
use App\Services\Subjects\SubjectCurriculumService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class SubjectCurriculumController extends BaseApiController
|
||||
{
|
||||
public function __construct(private SubjectCurriculumService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$data = $this->service->list();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'classes' => SubjectClassResource::collection($data['classes']),
|
||||
'entries' => SubjectCurriculumResource::collection($data['entries']),
|
||||
'subject_labels' => $data['subject_labels'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(int $id): JsonResponse
|
||||
{
|
||||
$entry = SubjectCurriculum::query()->find($id);
|
||||
if (!$entry) {
|
||||
return response()->json(['ok' => false, 'message' => 'Curriculum entry not found.'], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'entry' => new SubjectCurriculumResource($entry->toArray()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(SubjectCurriculumStoreRequest $request): JsonResponse
|
||||
{
|
||||
$entry = $this->service->store($request->validated());
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'entry' => new SubjectCurriculumResource($entry->toArray()),
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function update(SubjectCurriculumUpdateRequest $request, int $id): JsonResponse
|
||||
{
|
||||
$entry = $this->service->update($id, $request->validated());
|
||||
if (!$entry) {
|
||||
return response()->json(['ok' => false, 'message' => 'Curriculum entry not found.'], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'entry' => new SubjectCurriculumResource($entry->toArray()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(int $id): JsonResponse
|
||||
{
|
||||
$deleted = $this->service->delete($id);
|
||||
if (!$deleted) {
|
||||
return response()->json(['ok' => false, 'message' => 'Curriculum entry not found.'], 404);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\System;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\SchoolIds\SchoolIdAssignRequest;
|
||||
use App\Http\Resources\SchoolIds\SchoolIdResource;
|
||||
use App\Services\SchoolIds\SchoolIdAssignmentService;
|
||||
use App\Services\SchoolIds\SchoolIdGenerationService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class SchoolIdController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private SchoolIdAssignmentService $assignmentService,
|
||||
private SchoolIdGenerationService $generationService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function assign(SchoolIdAssignRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$schoolId = $this->assignmentService->assignToUser((int) $payload['user_id']);
|
||||
|
||||
if ($schoolId === null) {
|
||||
return response()->json(['ok' => false, 'message' => 'User not found.'], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'result' => new SchoolIdResource([
|
||||
'type' => 'user',
|
||||
'school_id' => $schoolId,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function generateStudent(): JsonResponse
|
||||
{
|
||||
$schoolId = $this->generationService->generateStudentId();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'result' => new SchoolIdResource([
|
||||
'type' => 'student',
|
||||
'school_id' => $schoolId,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function generateUser(): JsonResponse
|
||||
{
|
||||
$schoolId = $this->generationService->generateUserId();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'result' => new SchoolIdResource([
|
||||
'type' => 'user',
|
||||
'school_id' => $schoolId,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\EmergencyContacts;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class EmergencyContactUpdateRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:150'],
|
||||
'cellphone' => ['required', 'string', 'max:30'],
|
||||
'email' => ['nullable', 'email', 'max:150'],
|
||||
'relation' => ['nullable', 'string', 'max:80'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Exams;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class ExamDraftAdminLegacyRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'class_section_id' => ['required', 'integer', 'min:1'],
|
||||
'school_year' => ['required', 'string'],
|
||||
'semester' => ['required', 'string'],
|
||||
'exam_type' => ['nullable', 'string', 'max:50'],
|
||||
'old_exam_file' => ['required', 'file', 'mimes:doc,docx,pdf', 'max:12288'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Exams;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class ExamDraftAdminReviewRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'draft_id' => ['required', 'integer', 'min:1'],
|
||||
'review_status' => ['nullable', 'in:draft,submitted,reviewed,finalized,rejected,pending,final,approved'],
|
||||
'admin_comments' => ['nullable', 'string'],
|
||||
'final_file' => ['nullable', 'file', 'mimes:doc,docx,pdf', 'max:12288'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Exams;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class ExamDraftTeacherStoreRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'class_section_id' => ['required', 'integer', 'min:1'],
|
||||
'exam_type' => ['nullable', 'string', 'max:50'],
|
||||
'description' => ['nullable', 'string'],
|
||||
'draft_file' => ['nullable', 'file', 'mimes:doc,docx', 'max:12288'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Parents;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class ParentAttendanceReportCheckRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => ['nullable', 'date'],
|
||||
'dates' => ['nullable', 'array'],
|
||||
'dates.*' => ['date'],
|
||||
'type' => ['required', 'in:absent,late,early_dismissal'],
|
||||
'student_ids' => ['required', 'array'],
|
||||
'student_ids.*' => ['integer', 'min:1'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Parents;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class ParentAttendanceReportListRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => ['nullable', 'date'],
|
||||
'end' => ['nullable', 'date'],
|
||||
'school_year' => ['nullable', 'string', 'max:20'],
|
||||
'semester' => ['nullable', 'string', 'max:20'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Parents;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class ParentAttendanceReportSubmitRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'student_ids' => ['required', 'array'],
|
||||
'student_ids.*' => ['integer', 'min:1'],
|
||||
'date' => ['nullable', 'date'],
|
||||
'dates' => ['nullable', 'array'],
|
||||
'dates.*' => ['date'],
|
||||
'type' => ['required', 'in:absent,late,early_dismissal'],
|
||||
'arrival_time' => ['nullable', 'string'],
|
||||
'dismiss_time' => ['nullable', 'string'],
|
||||
'reason' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Parents;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class ParentAttendanceReportUpdateRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'reason' => ['nullable', 'string'],
|
||||
'arrival_time' => ['nullable', 'string'],
|
||||
'dismiss_time' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Parents;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class ParentAttendanceRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'school_year' => ['nullable', 'string', 'max:20'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Parents;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class ParentEmergencyContactRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:150'],
|
||||
'cellphone' => ['required', 'string', 'max:30'],
|
||||
'email' => ['nullable', 'email', 'max:150'],
|
||||
'relation' => ['nullable', 'string', 'max:80'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Parents;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class ParentEnrollmentActionRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'enroll' => ['nullable', 'array'],
|
||||
'enroll.*' => ['integer', 'min:1'],
|
||||
'withdraw' => ['nullable', 'array'],
|
||||
'withdraw.*' => ['integer', 'min:1'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Parents;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class ParentEnrollmentRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'school_year' => ['nullable', 'string', 'max:20'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Parents;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class ParentEventParticipationRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'participation' => ['required', 'array'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Parents;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class ParentInvoiceRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'school_year' => ['nullable', 'string', 'max:20'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Parents;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class ParentProfileUpdateRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'firstname' => ['required', 'string', 'max:255'],
|
||||
'lastname' => ['required', 'string', 'max:255'],
|
||||
'cellphone' => ['required', 'string', 'max:25'],
|
||||
'address_street' => ['required', 'string', 'max:255'],
|
||||
'city' => ['required', 'string', 'max:255'],
|
||||
'state' => ['required', 'string', 'max:25'],
|
||||
'zip' => ['required', 'string', 'max:25'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Parents;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class ParentRegistrationRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'students' => ['nullable', 'array'],
|
||||
'students.*.firstname' => ['required_with:students', 'string', 'max:255'],
|
||||
'students.*.lastname' => ['required_with:students', 'string', 'max:255'],
|
||||
'students.*.dob' => ['required_with:students', 'date'],
|
||||
'students.*.gender' => ['required_with:students', 'string', 'max:10'],
|
||||
'students.*.registration_grade' => ['nullable', 'string', 'max:50'],
|
||||
'students.*.photo_consent' => ['nullable', 'boolean'],
|
||||
'students.*.is_new' => ['nullable', 'boolean'],
|
||||
'students.*.allergies' => ['nullable', 'array'],
|
||||
'students.*.medical_conditions' => ['nullable', 'array'],
|
||||
'emergency_contacts' => ['nullable', 'array'],
|
||||
'emergency_contacts.*.name' => ['required_with:emergency_contacts', 'string', 'max:150'],
|
||||
'emergency_contacts.*.cellphone' => ['required_with:emergency_contacts', 'string', 'max:30'],
|
||||
'emergency_contacts.*.email' => ['nullable', 'email', 'max:150'],
|
||||
'emergency_contacts.*.relation' => ['nullable', 'string', 'max:80'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Parents;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class ParentStudentUpdateRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'firstname' => ['required', 'string', 'max:255'],
|
||||
'lastname' => ['required', 'string', 'max:255'],
|
||||
'dob' => ['required', 'date'],
|
||||
'gender' => ['required', 'string', 'max:10'],
|
||||
'photo_consent' => ['nullable', 'boolean'],
|
||||
'registration_grade' => ['nullable', 'string', 'max:50'],
|
||||
'allergies' => ['nullable', 'array'],
|
||||
'medical_conditions' => ['nullable', 'array'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Reports;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class SlipLogListRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'school_year' => ['nullable', 'string'],
|
||||
'semester' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Reports;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class SlipPreviewRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'school_year' => ['nullable', 'string'],
|
||||
'student_name' => ['nullable', 'string'],
|
||||
'date' => ['nullable', 'string'],
|
||||
'time_in' => ['nullable', 'string'],
|
||||
'grade' => ['nullable', 'string'],
|
||||
'reason' => ['nullable', 'string'],
|
||||
'admin_name' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Reports;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class SlipPrintRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'school_year' => ['nullable', 'string'],
|
||||
'student_name' => ['required', 'string'],
|
||||
'date' => ['nullable', 'string'],
|
||||
'time_in' => ['nullable', 'string'],
|
||||
'grade' => ['nullable', 'string'],
|
||||
'reason' => ['nullable', 'string'],
|
||||
'admin_name' => ['nullable', 'string'],
|
||||
'paper' => ['nullable', 'string'],
|
||||
'font_pt' => ['nullable', 'numeric'],
|
||||
'line_h' => ['nullable', 'numeric'],
|
||||
'rows' => ['nullable', 'integer'],
|
||||
'height_mm' => ['nullable', 'numeric'],
|
||||
'h' => ['nullable', 'numeric'],
|
||||
'fudge_mm' => ['nullable', 'numeric'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Reports;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class SlipReprintRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'id' => ['required', 'integer', 'min:1'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\SchoolIds;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class SchoolIdAssignRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'user_id' => ['required', 'integer', 'min:1', 'exists:users,id'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Settings;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class ConfigurationStoreRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'config_key' => ['required', 'string', 'max:255'],
|
||||
'config_value' => ['required', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Settings;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class ConfigurationUpdateRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'config_key' => ['required', 'string', 'max:255'],
|
||||
'config_value' => ['required', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Students;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class StudentAssignClassRequest extends ApiFormRequest
|
||||
{
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$value = $this->input('class_section_id');
|
||||
if (is_string($value)) {
|
||||
$parts = preg_split('/[,\s]+/', $value, -1, PREG_SPLIT_NO_EMPTY) ?: [];
|
||||
$this->merge(['class_section_id' => $parts]);
|
||||
}
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'student_id' => ['required', 'integer', 'min:1'],
|
||||
'class_section_id' => ['required', 'array', 'min:1'],
|
||||
'class_section_id.*' => ['integer', 'min:1'],
|
||||
'is_event_only' => ['nullable', 'boolean'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Students;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class StudentAssignmentListRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'school_year' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Students;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class StudentAutoDistributeRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'class_id' => ['nullable', 'integer', 'min:1'],
|
||||
'class_section_id' => ['nullable', 'integer', 'min:1'],
|
||||
'students_per_section' => ['required', 'integer', 'min:1'],
|
||||
'school_year' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Students;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class StudentPromotionTotalsRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'school_year' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Students;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class StudentRemoveClassRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'student_id' => ['required', 'integer', 'min:1'],
|
||||
'class_section_id' => ['required', 'integer', 'min:1'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Students;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class StudentSetActiveRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'student_id' => ['required', 'integer', 'min:1'],
|
||||
'is_active' => ['required', 'boolean'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Students;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class StudentUpdateRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'school_id' => ['nullable', 'string', 'max:100'],
|
||||
'firstname' => ['required', 'string', 'max:255'],
|
||||
'lastname' => ['required', 'string', 'max:255'],
|
||||
'dob' => ['required', 'date'],
|
||||
'age' => ['nullable', 'integer'],
|
||||
'gender' => ['required', 'in:Male,Female,Other'],
|
||||
'registration_grade' => ['required', 'string', 'max:50'],
|
||||
'photo_consent' => ['nullable', 'boolean'],
|
||||
'parent_id' => ['required', 'integer', 'min:1'],
|
||||
'registration_date' => ['nullable', 'date'],
|
||||
'tuition_paid' => ['nullable', 'boolean'],
|
||||
'year_of_registration' => ['nullable', 'string'],
|
||||
'school_year' => ['nullable', 'string'],
|
||||
'rfid_tag' => ['nullable', 'string', 'max:100'],
|
||||
'semester' => ['nullable', 'string'],
|
||||
'is_new' => ['required', 'boolean'],
|
||||
'is_active' => ['nullable', 'boolean'],
|
||||
'medical_conditions' => ['nullable', 'string'],
|
||||
'allergies' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Subjects;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class SubjectCurriculumStoreRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'class_id' => ['required', 'integer', 'min:1'],
|
||||
'subject' => ['required', 'in:islamic,quran'],
|
||||
'chapter_name' => ['required', 'string', 'max:255'],
|
||||
'unit_number' => ['nullable', 'integer'],
|
||||
'unit_title' => ['nullable', 'string', 'max:255'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Subjects;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class SubjectCurriculumUpdateRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'class_id' => ['required', 'integer', 'min:1'],
|
||||
'subject' => ['required', 'in:islamic,quran'],
|
||||
'chapter_name' => ['required', 'string', 'max:255'],
|
||||
'unit_number' => ['nullable', 'integer'],
|
||||
'unit_title' => ['nullable', 'string', 'max:255'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Teachers;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class TeacherAbsenceSubmitRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'dates' => ['required', 'array', 'min:1'],
|
||||
'dates.*' => ['date'],
|
||||
'reason' => ['required', 'string'],
|
||||
'reason_type' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Teachers;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class TeacherAssignmentDeleteRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'teacher_id' => ['required', 'integer', 'min:1'],
|
||||
'class_section_id' => ['required', 'integer', 'min:1'],
|
||||
'position' => ['required', 'in:main,ta'],
|
||||
'school_year' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Teachers;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class TeacherAssignmentListRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'school_year' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Teachers;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class TeacherAssignmentStoreRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'teacher_id' => ['required', 'integer', 'min:1'],
|
||||
'class_section_id' => ['required', 'integer', 'min:1'],
|
||||
'teacher_role' => ['nullable', 'string'],
|
||||
'school_year' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Teachers;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class TeacherClassViewRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'class_section_id' => ['nullable', 'integer', 'min:1'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Teachers;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
|
||||
class TeacherSwitchClassRequest extends ApiFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'class_section_id' => ['required', 'integer', 'min:1'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\EmergencyContacts;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class EmergencyContactGroupResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'parent_id' => (int) ($this['parent_id'] ?? 0),
|
||||
'parent_name' => (string) ($this['parent_name'] ?? ''),
|
||||
'parent_phones' => array_values($this['parent_phones'] ?? []),
|
||||
'students' => EmergencyContactStudentResource::collection($this['students'] ?? []),
|
||||
'contacts' => EmergencyContactResource::collection($this['contacts'] ?? []),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\EmergencyContacts;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class EmergencyContactResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) ($this['id'] ?? 0),
|
||||
'parent_id' => (int) ($this['parent_id'] ?? 0),
|
||||
'name' => (string) ($this['emergency_contact_name'] ?? ''),
|
||||
'cellphone' => (string) ($this['cellphone'] ?? ''),
|
||||
'email' => $this['email'] ?? null,
|
||||
'relation' => $this['relation'] ?? null,
|
||||
'semester' => $this['semester'] ?? null,
|
||||
'school_year' => $this['school_year'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\EmergencyContacts;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class EmergencyContactStudentResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) ($this['id'] ?? 0),
|
||||
'firstname' => (string) ($this['firstname'] ?? ''),
|
||||
'lastname' => (string) ($this['lastname'] ?? ''),
|
||||
'school_id' => $this['school_id'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Exams;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ExamDraftResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) ($this['id'] ?? 0),
|
||||
'teacher_id' => (int) ($this['teacher_id'] ?? 0),
|
||||
'class_section_id' => (int) ($this['class_section_id'] ?? 0),
|
||||
'class_section_name' => $this['class_section_name'] ?? null,
|
||||
'semester' => $this['semester'] ?? null,
|
||||
'school_year' => $this['school_year'] ?? null,
|
||||
'exam_type' => $this['exam_type'] ?? null,
|
||||
'draft_title' => $this['draft_title'] ?? null,
|
||||
'description' => $this['description'] ?? null,
|
||||
'teacher_file' => $this['teacher_file'] ?? null,
|
||||
'teacher_filename' => $this['teacher_filename'] ?? null,
|
||||
'status' => $this['status'] ?? null,
|
||||
'admin_id' => $this['admin_id'] ?? null,
|
||||
'admin_comments' => $this['admin_comments'] ?? null,
|
||||
'reviewed_at' => $this['reviewed_at'] ?? null,
|
||||
'final_file' => $this['final_file'] ?? null,
|
||||
'final_filename' => $this['final_filename'] ?? null,
|
||||
'final_pdf_file' => $this['final_pdf_file'] ?? null,
|
||||
'version' => $this['version'] ?? null,
|
||||
'previous_draft_id' => $this['previous_draft_id'] ?? null,
|
||||
'is_legacy' => $this['is_legacy'] ?? null,
|
||||
'teacher_first' => $this['teacher_first'] ?? null,
|
||||
'teacher_last' => $this['teacher_last'] ?? null,
|
||||
'admin_first' => $this['admin_first'] ?? null,
|
||||
'admin_last' => $this['admin_last'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Parents;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ParentAttendanceReportResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) ($this['id'] ?? 0),
|
||||
'parent_id' => (int) ($this['parent_id'] ?? 0),
|
||||
'student_id' => (int) ($this['student_id'] ?? 0),
|
||||
'class_section_id' => $this['class_section_id'] ?? null,
|
||||
'report_date' => (string) ($this['report_date'] ?? ''),
|
||||
'type' => (string) ($this['type'] ?? ''),
|
||||
'arrival_time' => $this['arrival_time'] ?? null,
|
||||
'dismiss_time' => $this['dismiss_time'] ?? null,
|
||||
'reason' => $this['reason'] ?? null,
|
||||
'semester' => $this['semester'] ?? null,
|
||||
'school_year' => $this['school_year'] ?? null,
|
||||
'status' => $this['status'] ?? null,
|
||||
'firstname' => $this['firstname'] ?? null,
|
||||
'lastname' => $this['lastname'] ?? null,
|
||||
'class_section_name' => $this['class_section_name'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Parents;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ParentAttendanceResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'firstname' => (string) ($this['firstname'] ?? ''),
|
||||
'lastname' => (string) ($this['lastname'] ?? ''),
|
||||
'date' => (string) ($this['date'] ?? ''),
|
||||
'status' => (string) ($this['status'] ?? ''),
|
||||
'reason' => $this['reason'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Parents;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ParentEmergencyContactResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) ($this['id'] ?? 0),
|
||||
'name' => (string) ($this['emergency_contact_name'] ?? ''),
|
||||
'cellphone' => (string) ($this['cellphone'] ?? ''),
|
||||
'email' => $this['email'] ?? null,
|
||||
'relation' => $this['relation'] ?? null,
|
||||
'semester' => $this['semester'] ?? null,
|
||||
'school_year' => $this['school_year'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Parents;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ParentStudentResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) ($this['id'] ?? 0),
|
||||
'firstname' => (string) ($this['firstname'] ?? ''),
|
||||
'lastname' => (string) ($this['lastname'] ?? ''),
|
||||
'dob' => $this['dob'] ?? null,
|
||||
'gender' => $this['gender'] ?? null,
|
||||
'registration_grade' => $this['registration_grade'] ?? null,
|
||||
'school_year' => $this['school_year'] ?? null,
|
||||
'semester' => $this['semester'] ?? null,
|
||||
'class_section' => $this['class_section'] ?? null,
|
||||
'enrollment_status' => $this['enrollment_status'] ?? null,
|
||||
'admission_status' => $this['admission_status'] ?? null,
|
||||
'disable_enroll' => (bool) ($this['disable_enroll'] ?? false),
|
||||
'allergies' => $this['allergies'] ?? [],
|
||||
'medical_conditions' => $this['medical_conditions'] ?? [],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Reports;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class LateSlipLogResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) ($this['id'] ?? 0),
|
||||
'school_year' => (string) ($this['school_year'] ?? ''),
|
||||
'semester' => (string) ($this['semester'] ?? ''),
|
||||
'student_name' => (string) ($this['student_name'] ?? ''),
|
||||
'date' => $this['date'] ?? null,
|
||||
'time_in' => $this['time_in'] ?? null,
|
||||
'grade' => $this['grade'] ?? null,
|
||||
'reason' => $this['reason'] ?? null,
|
||||
'admin_name' => $this['admin_name'] ?? null,
|
||||
'printed_at' => $this['printed_at'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\SchoolIds;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class SchoolIdResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'type' => $this['type'] ?? null,
|
||||
'school_id' => $this['school_id'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Settings;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ConfigurationResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) ($this['id'] ?? 0),
|
||||
'config_key' => (string) ($this['config_key'] ?? ''),
|
||||
'config_value' => (string) ($this['config_value'] ?? ''),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Students;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class StudentAssignmentResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'student_id' => (int) ($this['student_id'] ?? 0),
|
||||
'name' => (string) ($this['name'] ?? ''),
|
||||
'age' => $this['age'] ?? null,
|
||||
'email' => $this['email'] ?? null,
|
||||
'phone' => $this['phone'] ?? null,
|
||||
'registration_grade' => $this['registration_grade'] ?? null,
|
||||
'class_section_name' => $this['class_section_name'] ?? null,
|
||||
'class_section_names' => $this['class_section_names'] ?? [],
|
||||
'class_section_ids' => $this['class_section_ids'] ?? [],
|
||||
'new_student' => $this['new_student'] ?? null,
|
||||
'registration_date' => $this['registration_date'] ?? null,
|
||||
'school_year' => $this['school_year'] ?? null,
|
||||
'semester' => $this['semester'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Students;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class StudentClassSectionResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) ($this['id'] ?? 0),
|
||||
'class_section_id' => (int) ($this['class_section_id'] ?? 0),
|
||||
'class_section_name' => (string) ($this['class_section_name'] ?? ''),
|
||||
'school_year' => $this['school_year'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Students;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class StudentRemovedResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) ($this['id'] ?? 0),
|
||||
'school_id' => $this['school_id'] ?? null,
|
||||
'firstname' => $this['firstname'] ?? null,
|
||||
'lastname' => $this['lastname'] ?? null,
|
||||
'gender' => $this['gender'] ?? null,
|
||||
'age' => $this['age'] ?? null,
|
||||
'class_sections' => $this['class_sections'] ?? [],
|
||||
'class_section_name' => $this['class_section_name'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Students;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class StudentScoreCardRowResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'school_year' => $this['school_year'] ?? null,
|
||||
'semester' => $this['semester'] ?? null,
|
||||
'homework_avg' => $this['homework_avg'] ?? null,
|
||||
'project_avg' => $this['project_avg'] ?? null,
|
||||
'participation_score' => $this['participation_score'] ?? null,
|
||||
'quiz_avg' => $this['quiz_avg'] ?? null,
|
||||
'test_avg' => $this['test_avg'] ?? null,
|
||||
'attendance_score' => $this['attendance_score'] ?? null,
|
||||
'ptap_score' => $this['ptap_score'] ?? null,
|
||||
'midterm_exam_score' => $this['midterm_exam_score'] ?? null,
|
||||
'semester_score' => $this['semester_score'] ?? null,
|
||||
'class_section_name' => $this['class_section_name'] ?? null,
|
||||
'comments' => $this['comments'] ?? [],
|
||||
'year_score' => $this['year_score'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Subjects;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class SubjectClassResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) ($this['id'] ?? 0),
|
||||
'class_name' => (string) ($this['class_name'] ?? ''),
|
||||
'school_year' => $this['school_year'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Subjects;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class SubjectCurriculumResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) ($this['id'] ?? 0),
|
||||
'class_id' => (int) ($this['class_id'] ?? 0),
|
||||
'class_name' => $this['class_name'] ?? null,
|
||||
'subject' => (string) ($this['subject'] ?? ''),
|
||||
'unit_number' => $this['unit_number'] ?? null,
|
||||
'unit_title' => $this['unit_title'] ?? null,
|
||||
'chapter_name' => (string) ($this['chapter_name'] ?? ''),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Teachers;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class TeacherAbsenceResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) ($this['id'] ?? 0),
|
||||
'user_id' => (int) ($this['user_id'] ?? 0),
|
||||
'date' => (string) ($this['date'] ?? ''),
|
||||
'semester' => $this['semester'] ?? null,
|
||||
'school_year' => $this['school_year'] ?? null,
|
||||
'status' => $this['status'] ?? null,
|
||||
'reason' => $this['reason'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Teachers;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class TeacherAssignmentResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'teacher_id' => (int) ($this['teacher_id'] ?? 0),
|
||||
'firstname' => (string) ($this['firstname'] ?? ''),
|
||||
'lastname' => (string) ($this['lastname'] ?? ''),
|
||||
'name' => (string) ($this['name'] ?? ''),
|
||||
'email' => $this['email'] ?? null,
|
||||
'cellphone' => $this['cellphone'] ?? null,
|
||||
'role' => $this['role'] ?? null,
|
||||
'school_year' => $this['school_year'] ?? null,
|
||||
'main_assignments' => $this['main_assignments'] ?? [],
|
||||
'ta_assignments' => $this['ta_assignments'] ?? [],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Teachers;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class TeacherClassResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'class_section_id' => (int) ($this['class_section_id'] ?? 0),
|
||||
'class_section_name' => (string) ($this['class_section_name'] ?? ''),
|
||||
'class_id' => $this['class_id'] ?? null,
|
||||
'class_name' => $this['class_name'] ?? null,
|
||||
'teacher_role' => $this['teacher_role'] ?? null,
|
||||
'school_year' => $this['school_year'] ?? null,
|
||||
'semester' => $this['semester'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Teachers;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class TeacherStudentResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'student_id' => (int) ($this['student_id'] ?? 0),
|
||||
'firstname' => (string) ($this['firstname'] ?? ''),
|
||||
'lastname' => (string) ($this['lastname'] ?? ''),
|
||||
'school_id' => $this['school_id'] ?? null,
|
||||
'is_new' => $this['is_new'] ?? null,
|
||||
'photo_consent' => $this['photo_consent'] ?? null,
|
||||
'age' => $this['age'] ?? null,
|
||||
'school_year' => $this['school_year'] ?? null,
|
||||
'class_section_id' => $this['class_section_id'] ?? null,
|
||||
'medical_conditions' => $this['medical_conditions'] ?? [],
|
||||
'allergies' => $this['allergies'] ?? [],
|
||||
'medical_conditions_text' => $this['medical_conditions_text'] ?? '',
|
||||
'allergies_text' => $this['allergies_text'] ?? '',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ class ExamDraft extends BaseModel
|
||||
'reviewed_at',
|
||||
'final_file',
|
||||
'final_filename',
|
||||
'final_pdf_file',
|
||||
'version',
|
||||
'previous_draft_id',
|
||||
'is_legacy',
|
||||
@@ -87,4 +88,4 @@ class ExamDraft extends BaseModel
|
||||
{
|
||||
return $this->belongsTo(self::class, 'previous_draft_id');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\EmergencyContacts;
|
||||
|
||||
use App\Models\EmergencyContact;
|
||||
use App\Services\PhoneFormatterService;
|
||||
|
||||
class EmergencyContactCrudService
|
||||
{
|
||||
public function __construct(
|
||||
private PhoneFormatterService $phoneFormatter
|
||||
) {
|
||||
}
|
||||
|
||||
public function update(int $contactId, array $payload): array
|
||||
{
|
||||
$contact = EmergencyContact::query()->findOrFail($contactId);
|
||||
|
||||
$contact->update([
|
||||
'emergency_contact_name' => $payload['name'],
|
||||
'cellphone' => $this->phoneFormatter->formatPhoneNumber($payload['cellphone']),
|
||||
'email' => $payload['email'] ?? null,
|
||||
'relation' => $payload['relation'] ?? null,
|
||||
]);
|
||||
|
||||
return $contact->toArray();
|
||||
}
|
||||
|
||||
public function delete(int $contactId): void
|
||||
{
|
||||
$contact = EmergencyContact::query()->findOrFail($contactId);
|
||||
$contact->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\EmergencyContacts;
|
||||
|
||||
use App\Models\EmergencyContact;
|
||||
use App\Models\Student;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class EmergencyContactDirectoryService
|
||||
{
|
||||
public function groups(): array
|
||||
{
|
||||
$parentIds = EmergencyContact::query()
|
||||
->distinct()
|
||||
->pluck('parent_id')
|
||||
->filter(static fn ($id) => (int) $id > 0)
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$groups = [];
|
||||
foreach ($parentIds as $parentId) {
|
||||
$parentId = (int) $parentId;
|
||||
$parent = User::query()->find($parentId);
|
||||
|
||||
$parentName = $parent
|
||||
? trim((string) $parent->firstname . ' ' . (string) $parent->lastname)
|
||||
: 'Unknown Parent';
|
||||
if ($parentName === '') {
|
||||
$parentName = 'Unknown Parent';
|
||||
}
|
||||
|
||||
$parentPhone = $parent ? (string) ($parent->cellphone ?? '') : '';
|
||||
$secondPhone = $this->secondParentPhone($parentId);
|
||||
|
||||
$students = Student::query()
|
||||
->select(['id', 'firstname', 'lastname', 'school_id'])
|
||||
->where('parent_id', $parentId)
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
$contacts = EmergencyContact::query()
|
||||
->where('parent_id', $parentId)
|
||||
->orderBy('updated_at', 'DESC')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
$groups[] = [
|
||||
'parent_id' => $parentId,
|
||||
'parent_name' => $parentName,
|
||||
'parent_phones' => array_values(array_filter([
|
||||
$parentPhone,
|
||||
$secondPhone,
|
||||
], static fn ($value) => (string) $value !== '')),
|
||||
'students' => $students,
|
||||
'contacts' => $contacts,
|
||||
];
|
||||
}
|
||||
|
||||
return $groups;
|
||||
}
|
||||
|
||||
private function secondParentPhone(int $parentId): string
|
||||
{
|
||||
try {
|
||||
if (!Schema::hasTable('parents')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!Schema::hasColumn('parents', 'secondparent_phone')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$phone = DB::table('parents')
|
||||
->where('firstparent_id', $parentId)
|
||||
->orderByDesc('updated_at')
|
||||
->value('secondparent_phone');
|
||||
|
||||
return $phone ? (string) $phone : '';
|
||||
} catch (\Throwable $e) {
|
||||
Log::debug('EmergencyContactDirectoryService: could not load second parent phone: ' . $e->getMessage());
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Exams;
|
||||
|
||||
use App\Models\ClassSection;
|
||||
use App\Models\ExamDraft;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ExamDraftAdminService
|
||||
{
|
||||
public function __construct(
|
||||
private ExamDraftConfigService $configService,
|
||||
private ExamDraftFileService $fileService
|
||||
) {
|
||||
}
|
||||
|
||||
public function list(): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
|
||||
$drafts = DB::table('exam_drafts as ed')
|
||||
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'ed.class_section_id')
|
||||
->leftJoin('users as u', 'u.id', '=', 'ed.teacher_id')
|
||||
->leftJoin('users as a', 'a.id', '=', 'ed.admin_id')
|
||||
->select(
|
||||
'ed.*',
|
||||
'cs.class_section_name',
|
||||
'u.firstname as teacher_first',
|
||||
'u.lastname as teacher_last',
|
||||
'a.firstname as admin_first',
|
||||
'a.lastname as admin_last'
|
||||
)
|
||||
->orderByDesc('ed.created_at')
|
||||
->get()
|
||||
->map(fn ($r) => (array) $r)
|
||||
->all();
|
||||
|
||||
$classSections = ClassSection::query()
|
||||
->select('class_section_id', 'class_section_name')
|
||||
->orderBy('class_section_name')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
$legacyByClass = [];
|
||||
if ($context['has_legacy'] ?? false) {
|
||||
foreach ($drafts as $row) {
|
||||
$isLegacy = !empty($row['is_legacy']);
|
||||
if (!$isLegacy) {
|
||||
continue;
|
||||
}
|
||||
$cid = (int) ($row['class_section_id'] ?? 0);
|
||||
if (!isset($legacyByClass[$cid])) {
|
||||
$legacyByClass[$cid] = [
|
||||
'class_section_id' => $cid,
|
||||
'class_section_name' => $row['class_section_name'] ?? 'Class ' . $cid,
|
||||
'items' => [],
|
||||
];
|
||||
}
|
||||
$legacyByClass[$cid]['items'][] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'drafts' => $drafts,
|
||||
'classSections' => $classSections,
|
||||
'legacyByClass' => $legacyByClass,
|
||||
'examTypes' => ExamDraftConfigService::EXAM_TYPES,
|
||||
'statusOptions' => ExamDraftConfigService::STATUS_OPTIONS,
|
||||
'schoolYear' => (string) ($context['school_year'] ?? ''),
|
||||
'semester' => (string) ($context['semester'] ?? ''),
|
||||
'allowedExtensions' => ExamDraftConfigService::ADMIN_ALLOWED_EXTENSIONS,
|
||||
'maxUploadBytes' => ExamDraftConfigService::MAX_UPLOAD_BYTES,
|
||||
];
|
||||
}
|
||||
|
||||
public function uploadLegacy(int $adminId, array $payload, \Illuminate\Http\UploadedFile $file): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$classSectionId = (int) ($payload['class_section_id'] ?? 0);
|
||||
$schoolYear = trim((string) ($payload['school_year'] ?? $context['school_year'] ?? ''));
|
||||
$semester = trim((string) ($payload['semester'] ?? $context['semester'] ?? ''));
|
||||
$examType = trim((string) ($payload['exam_type'] ?? ''));
|
||||
|
||||
if ($classSectionId <= 0 || $schoolYear === '' || $semester === '') {
|
||||
return ['ok' => false, 'message' => 'Select class section, school year, and semester.'];
|
||||
}
|
||||
|
||||
$stored = $this->fileService->storeUploadedFile(
|
||||
$file,
|
||||
ExamDraftConfigService::FINAL_UPLOAD_DIR,
|
||||
ExamDraftConfigService::ADMIN_ALLOWED_EXTENSIONS,
|
||||
ExamDraftConfigService::MAX_UPLOAD_BYTES
|
||||
);
|
||||
if ($stored === null) {
|
||||
return ['ok' => false, 'message' => 'File type not allowed or upload failed.'];
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'teacher_id' => $adminId,
|
||||
'class_section_id' => $classSectionId,
|
||||
'semester' => ucfirst(strtolower($semester)),
|
||||
'school_year' => $schoolYear,
|
||||
'exam_type' => $examType !== '' ? $examType : null,
|
||||
'draft_title' => $examType !== '' ? $examType : 'Legacy Exam',
|
||||
'final_file' => $stored['stored'],
|
||||
'final_filename' => $stored['original'],
|
||||
'status' => 'finalized',
|
||||
'admin_id' => $adminId,
|
||||
'reviewed_at' => now(),
|
||||
'version' => 1,
|
||||
];
|
||||
|
||||
if ($context['has_legacy'] ?? false) {
|
||||
$payload['is_legacy'] = 1;
|
||||
}
|
||||
|
||||
$pdfName = null;
|
||||
if ($stored['extension'] === 'pdf') {
|
||||
$pdfName = $stored['stored'];
|
||||
} else {
|
||||
$pdfName = $this->fileService->ensurePdfExists(
|
||||
$stored['stored'],
|
||||
$stored['extension'],
|
||||
ExamDraftConfigService::FINAL_UPLOAD_DIR
|
||||
);
|
||||
}
|
||||
if ($pdfName !== null && ($context['has_final_pdf'] ?? false)) {
|
||||
$payload['final_pdf_file'] = $pdfName;
|
||||
}
|
||||
|
||||
$draft = ExamDraft::query()->create($payload);
|
||||
|
||||
return ['ok' => true, 'draft' => $draft->toArray()];
|
||||
}
|
||||
|
||||
public function review(int $adminId, array $payload, ?\Illuminate\Http\UploadedFile $file): array
|
||||
{
|
||||
$draftId = (int) ($payload['draft_id'] ?? 0);
|
||||
if ($draftId <= 0) {
|
||||
return ['ok' => false, 'message' => 'Invalid submission selected.'];
|
||||
}
|
||||
|
||||
$draft = ExamDraft::query()->find($draftId);
|
||||
if (!$draft) {
|
||||
return ['ok' => false, 'message' => 'Submission not found.'];
|
||||
}
|
||||
|
||||
$comments = trim((string) ($payload['admin_comments'] ?? ''));
|
||||
$statusInput = trim((string) ($payload['review_status'] ?? ''));
|
||||
$currentStatus = (string) ($draft->status ?? 'draft');
|
||||
$status = $this->normalizeStatus($statusInput !== '' ? $statusInput : $currentStatus, $currentStatus);
|
||||
if (!in_array($status, ExamDraftConfigService::STATUS_OPTIONS, true)) {
|
||||
$status = 'reviewed';
|
||||
}
|
||||
|
||||
$update = [
|
||||
'status' => $status,
|
||||
'admin_comments' => $comments !== '' ? $comments : null,
|
||||
'admin_id' => $adminId,
|
||||
'reviewed_at' => now(),
|
||||
];
|
||||
|
||||
if ($file) {
|
||||
$stored = $this->fileService->storeUploadedFile(
|
||||
$file,
|
||||
ExamDraftConfigService::FINAL_UPLOAD_DIR,
|
||||
ExamDraftConfigService::ADMIN_ALLOWED_EXTENSIONS,
|
||||
ExamDraftConfigService::MAX_UPLOAD_BYTES
|
||||
);
|
||||
if ($stored === null) {
|
||||
return ['ok' => false, 'message' => 'Unable to store the final draft.'];
|
||||
}
|
||||
$update['final_file'] = $stored['stored'];
|
||||
$update['final_filename'] = $stored['original'];
|
||||
$pdfName = $this->fileService->ensurePdfExists(
|
||||
$stored['stored'],
|
||||
$stored['extension'],
|
||||
ExamDraftConfigService::FINAL_UPLOAD_DIR
|
||||
);
|
||||
if ($pdfName !== null && ($this->configService->context()['has_final_pdf'] ?? false)) {
|
||||
$update['final_pdf_file'] = $pdfName;
|
||||
}
|
||||
} elseif ($status === 'finalized' && !empty($draft->teacher_file)) {
|
||||
$copied = $this->fileService->copyDraftToFinal(
|
||||
(string) $draft->teacher_file,
|
||||
ExamDraftConfigService::TEACHER_UPLOAD_DIR,
|
||||
ExamDraftConfigService::FINAL_UPLOAD_DIR
|
||||
);
|
||||
if ($copied !== null) {
|
||||
$update['final_file'] = $copied;
|
||||
$update['final_filename'] = $draft->teacher_filename ?? $draft->teacher_file;
|
||||
$pdfName = $this->fileService->ensurePdfExists(
|
||||
$copied,
|
||||
pathinfo($copied, PATHINFO_EXTENSION),
|
||||
ExamDraftConfigService::FINAL_UPLOAD_DIR
|
||||
);
|
||||
if ($pdfName !== null && ($this->configService->context()['has_final_pdf'] ?? false)) {
|
||||
$update['final_pdf_file'] = $pdfName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($status === 'finalized' && ($this->configService->context()['has_legacy'] ?? false)) {
|
||||
$update['is_legacy'] = 1;
|
||||
}
|
||||
|
||||
$draft->update($update);
|
||||
|
||||
return ['ok' => true, 'draft' => $draft->fresh()->toArray()];
|
||||
}
|
||||
|
||||
private function normalizeStatus(string $input, string $default): string
|
||||
{
|
||||
$input = strtolower(trim($input));
|
||||
$aliases = [
|
||||
'pending' => 'submitted',
|
||||
'final' => 'finalized',
|
||||
'approved' => 'finalized',
|
||||
];
|
||||
if (isset($aliases[$input])) {
|
||||
$input = $aliases[$input];
|
||||
}
|
||||
return in_array($input, ExamDraftConfigService::STATUS_OPTIONS, true) ? $input : $default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Exams;
|
||||
|
||||
use App\Models\Configuration;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ExamDraftConfigService
|
||||
{
|
||||
public const TEACHER_UPLOAD_DIR = 'exams/drafts';
|
||||
public const FINAL_UPLOAD_DIR = 'exams/finals';
|
||||
public const MAX_UPLOAD_BYTES = 12 * 1024 * 1024;
|
||||
public const ALLOWED_EXTENSIONS = ['doc', 'docx'];
|
||||
public const ADMIN_ALLOWED_EXTENSIONS = ['doc', 'docx', 'pdf'];
|
||||
|
||||
public const EXAM_TYPES = [
|
||||
'Final Exam',
|
||||
'Midterm Exam',
|
||||
'Quiz',
|
||||
'Study Guide',
|
||||
'Practice Exam',
|
||||
'Other',
|
||||
];
|
||||
|
||||
public const STATUS_OPTIONS = ['draft', 'submitted', 'reviewed', 'finalized', 'rejected'];
|
||||
|
||||
public function context(): array
|
||||
{
|
||||
return [
|
||||
'school_year' => (string) (Configuration::getConfig('school_year') ?? ''),
|
||||
'semester' => (string) (Configuration::getConfig('semester') ?? ''),
|
||||
'has_final_pdf' => $this->hasColumn('exam_drafts', 'final_pdf_file'),
|
||||
'has_legacy' => $this->hasColumn('exam_drafts', 'is_legacy'),
|
||||
];
|
||||
}
|
||||
|
||||
private function hasColumn(string $table, string $column): bool
|
||||
{
|
||||
try {
|
||||
return Schema::hasColumn($table, $column);
|
||||
} catch (\Throwable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Exams;
|
||||
|
||||
use Illuminate\Http\UploadedFile;
|
||||
|
||||
class ExamDraftFileService
|
||||
{
|
||||
public function storeUploadedFile(UploadedFile $file, string $subdir, array $allowedExtensions, int $maxBytes): ?array
|
||||
{
|
||||
$ext = strtolower($file->getClientOriginalExtension());
|
||||
if (!in_array($ext, $allowedExtensions, true)) {
|
||||
return null;
|
||||
}
|
||||
if ($file->getSize() > $maxBytes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$destination = storage_path('uploads/' . trim($subdir, '/'));
|
||||
if (!is_dir($destination)) {
|
||||
mkdir($destination, 0755, true);
|
||||
}
|
||||
|
||||
$filename = uniqid('exam_', true) . '.' . $ext;
|
||||
try {
|
||||
$file->move($destination, $filename);
|
||||
return [
|
||||
'stored' => $filename,
|
||||
'original' => $file->getClientOriginalName(),
|
||||
'extension' => $ext,
|
||||
];
|
||||
} catch (\Throwable) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function ensurePdfExists(string $finalFilename, ?string $originalExt, string $subdir): ?string
|
||||
{
|
||||
$pdfNeighbor = $this->neighborPdfIfExists($finalFilename, $subdir);
|
||||
if ($pdfNeighbor !== null) {
|
||||
return $pdfNeighbor;
|
||||
}
|
||||
$ext = strtolower((string) $originalExt);
|
||||
if ($ext === 'pdf') {
|
||||
$path = $this->fullUploadPath($subdir, $finalFilename);
|
||||
return is_file($path) ? $finalFilename : null;
|
||||
}
|
||||
return $this->convertDocToPdf($this->fullUploadPath($subdir, $finalFilename), $subdir);
|
||||
}
|
||||
|
||||
public function copyDraftToFinal(string $draftFilename, string $sourceDir, string $targetDir): ?string
|
||||
{
|
||||
$source = $this->fullUploadPath($sourceDir, $draftFilename);
|
||||
if (!is_file($source)) {
|
||||
return null;
|
||||
}
|
||||
$destinationDir = storage_path('uploads/' . trim($targetDir, '/'));
|
||||
if (!is_dir($destinationDir)) {
|
||||
mkdir($destinationDir, 0755, true);
|
||||
}
|
||||
$ext = pathinfo($draftFilename, PATHINFO_EXTENSION);
|
||||
$destName = uniqid('final_', true) . '.' . $ext;
|
||||
$destPath = $destinationDir . '/' . $destName;
|
||||
if (!@copy($source, $destPath)) {
|
||||
return null;
|
||||
}
|
||||
return $destName;
|
||||
}
|
||||
|
||||
private function convertDocToPdf(string $sourcePath, string $targetSubdir): ?string
|
||||
{
|
||||
$ext = strtolower(pathinfo($sourcePath, PATHINFO_EXTENSION));
|
||||
if (!in_array($ext, ['doc', 'docx'], true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$targetDir = storage_path('uploads/' . trim($targetSubdir, '/'));
|
||||
if (!is_dir($targetDir)) {
|
||||
mkdir($targetDir, 0755, true);
|
||||
}
|
||||
|
||||
$base = pathinfo($sourcePath, PATHINFO_FILENAME);
|
||||
$targetPath = $targetDir . '/' . $base . '.pdf';
|
||||
|
||||
$cmd = 'soffice --headless --convert-to pdf --outdir ' . escapeshellarg($targetDir) . ' ' . escapeshellarg($sourcePath) . ' 2>/dev/null';
|
||||
@exec($cmd);
|
||||
|
||||
return is_file($targetPath) ? basename($targetPath) : null;
|
||||
}
|
||||
|
||||
private function neighborPdfIfExists(string $filename, string $subdir): ?string
|
||||
{
|
||||
if ($filename === '') {
|
||||
return null;
|
||||
}
|
||||
$path = $this->fullUploadPath($subdir, $filename);
|
||||
$base = pathinfo($path, PATHINFO_FILENAME);
|
||||
$dir = pathinfo($path, PATHINFO_DIRNAME);
|
||||
$pdfPath = $dir . '/' . $base . '.pdf';
|
||||
return is_file($pdfPath) ? basename($pdfPath) : null;
|
||||
}
|
||||
|
||||
private function fullUploadPath(string $subdir, string $filename): string
|
||||
{
|
||||
return storage_path('uploads/' . trim($subdir, '/') . '/' . $filename);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Exams;
|
||||
|
||||
use App\Models\ExamDraft;
|
||||
use App\Models\TeacherClass;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ExamDraftTeacherService
|
||||
{
|
||||
public function __construct(
|
||||
private ExamDraftConfigService $configService,
|
||||
private ExamDraftFileService $fileService
|
||||
) {
|
||||
}
|
||||
|
||||
public function listForTeacher(int $teacherId): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$schoolYear = (string) ($context['school_year'] ?? '');
|
||||
|
||||
$assignments = TeacherClass::getClassAssignmentsByUserId($teacherId, $schoolYear);
|
||||
$classSectionIds = array_map(static fn ($a) => (int) $a['class_section_id'], $assignments);
|
||||
|
||||
$drafts = ExamDraft::query()
|
||||
->where('teacher_id', $teacherId)
|
||||
->orderByDesc('created_at')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
$legacyExams = [];
|
||||
if (($context['has_legacy'] ?? false) && !empty($classSectionIds)) {
|
||||
$legacyExams = DB::table('exam_drafts as ed')
|
||||
->select('ed.*', 'cs.class_section_name')
|
||||
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'ed.class_section_id')
|
||||
->whereIn('ed.class_section_id', $classSectionIds)
|
||||
->where('ed.is_legacy', 1)
|
||||
->whereNotNull('ed.final_file')
|
||||
->orderBy('cs.class_section_name')
|
||||
->orderByDesc('ed.created_at')
|
||||
->get()
|
||||
->map(fn ($r) => (array) $r)
|
||||
->all();
|
||||
}
|
||||
|
||||
return [
|
||||
'assignments' => $assignments,
|
||||
'drafts' => $drafts,
|
||||
'legacyExams' => $legacyExams,
|
||||
'examTypes' => ExamDraftConfigService::EXAM_TYPES,
|
||||
'statusOptions' => ExamDraftConfigService::STATUS_OPTIONS,
|
||||
'schoolYear' => $schoolYear,
|
||||
'semester' => (string) ($context['semester'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
public function store(int $teacherId, array $payload, ?\Illuminate\Http\UploadedFile $file): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$schoolYear = (string) ($context['school_year'] ?? '');
|
||||
$semester = (string) ($context['semester'] ?? '');
|
||||
|
||||
$classSectionId = (int) ($payload['class_section_id'] ?? 0);
|
||||
$examType = trim((string) ($payload['exam_type'] ?? ''));
|
||||
$description = trim((string) ($payload['description'] ?? ''));
|
||||
|
||||
if ($classSectionId <= 0) {
|
||||
return ['ok' => false, 'message' => 'Select a class section before submitting.'];
|
||||
}
|
||||
|
||||
$assignment = TeacherClass::query()
|
||||
->where('teacher_id', $teacherId)
|
||||
->where('class_section_id', $classSectionId)
|
||||
->first();
|
||||
|
||||
if (!$assignment) {
|
||||
return ['ok' => false, 'message' => 'You are not assigned to the selected class section.'];
|
||||
}
|
||||
|
||||
$teacherFile = null;
|
||||
$teacherFilename = null;
|
||||
if ($file) {
|
||||
$stored = $this->fileService->storeUploadedFile(
|
||||
$file,
|
||||
ExamDraftConfigService::TEACHER_UPLOAD_DIR,
|
||||
ExamDraftConfigService::ALLOWED_EXTENSIONS,
|
||||
ExamDraftConfigService::MAX_UPLOAD_BYTES
|
||||
);
|
||||
if ($stored === null) {
|
||||
return ['ok' => false, 'message' => 'Failed to store the uploaded file.'];
|
||||
}
|
||||
$teacherFile = $stored['stored'];
|
||||
$teacherFilename = $stored['original'];
|
||||
}
|
||||
|
||||
$existingDraft = ExamDraft::query()
|
||||
->where('teacher_id', $teacherId)
|
||||
->where('class_section_id', $classSectionId)
|
||||
->where('semester', $semester)
|
||||
->where('school_year', $schoolYear)
|
||||
->orderByDesc('version')
|
||||
->first();
|
||||
|
||||
$nextVersion = 1;
|
||||
$previousId = null;
|
||||
if ($existingDraft) {
|
||||
$nextVersion = ((int) ($existingDraft->version ?? 1)) + 1;
|
||||
$previousId = (int) ($existingDraft->id ?? 0) ?: null;
|
||||
}
|
||||
|
||||
$title = $examType !== '' ? $examType : 'Exam Draft';
|
||||
|
||||
$draft = ExamDraft::query()->create([
|
||||
'teacher_id' => $teacherId,
|
||||
'class_section_id' => $classSectionId,
|
||||
'semester' => $semester,
|
||||
'school_year' => $schoolYear,
|
||||
'exam_type' => $examType !== '' ? $examType : null,
|
||||
'draft_title' => $title,
|
||||
'description' => $description !== '' ? $description : null,
|
||||
'teacher_file' => $teacherFile,
|
||||
'teacher_filename' => $teacherFilename,
|
||||
'status' => 'submitted',
|
||||
'version' => $nextVersion,
|
||||
'previous_draft_id' => $previousId,
|
||||
]);
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'draft' => $draft->toArray(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Parents;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ParentAttendanceReportCalendarService
|
||||
{
|
||||
public function __construct(private ParentConfigService $configService)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: array<int, array{value:string,label:string}>, 1: string}
|
||||
*/
|
||||
public function computeSundays(int $weeksBefore = 4, int $weeksAfter = 26): array
|
||||
{
|
||||
$today = new \DateTime('today');
|
||||
$dow = (int) $today->format('w');
|
||||
|
||||
$thisSunday = clone $today;
|
||||
if ($dow !== 0) {
|
||||
$thisSunday->modify('last sunday');
|
||||
}
|
||||
|
||||
$default = clone $today;
|
||||
if ($dow !== 0) {
|
||||
$default->modify('next sunday');
|
||||
}
|
||||
|
||||
$schoolYear = (string) ($this->configService->context()['school_year'] ?? '');
|
||||
$cap = $this->firstSundayOfJune($schoolYear);
|
||||
|
||||
$dates = [];
|
||||
for ($i = $weeksBefore; $i >= 1; $i--) {
|
||||
$dates[] = (clone $thisSunday)->modify('-' . $i . ' week');
|
||||
}
|
||||
|
||||
$cursor = clone $thisSunday;
|
||||
while ($cursor <= $cap) {
|
||||
$dates[] = clone $cursor;
|
||||
$cursor->modify('+1 week');
|
||||
}
|
||||
|
||||
$rangeStart = reset($dates);
|
||||
$rangeEnd = end($dates);
|
||||
$rangeStartStr = $rangeStart instanceof \DateTime ? $rangeStart->format('Y-m-d') : null;
|
||||
$rangeEndStr = $rangeEnd instanceof \DateTime ? $rangeEnd->format('Y-m-d') : null;
|
||||
|
||||
$noSchool = [];
|
||||
if ($rangeStartStr && $rangeEndStr) {
|
||||
$rows = DB::table('calendar_events')
|
||||
->select('date')
|
||||
->where('no_school', 1)
|
||||
->groupStart()
|
||||
->where('school_year', $schoolYear)
|
||||
->orWhere('school_year IS NULL', null, false)
|
||||
->groupEnd()
|
||||
->where('date >=', $rangeStartStr)
|
||||
->where('date <=', $rangeEndStr)
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$d = substr((string) ($row->date ?? ''), 0, 10);
|
||||
if ($d !== '') {
|
||||
$noSchool[$d] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$filtered = [];
|
||||
foreach ($dates as $d) {
|
||||
$ymd = $d->format('Y-m-d');
|
||||
if (!isset($noSchool[$ymd]) && $d >= $today && $d <= $cap) {
|
||||
$filtered[] = $d;
|
||||
}
|
||||
}
|
||||
|
||||
$defaultYmd = $default->format('Y-m-d');
|
||||
if (!empty($filtered)) {
|
||||
$defaultYmd = $filtered[0]->format('Y-m-d');
|
||||
}
|
||||
|
||||
$out = [];
|
||||
foreach ($filtered as $d) {
|
||||
$out[] = [
|
||||
'value' => $d->format('Y-m-d'),
|
||||
'label' => $d->format('m-d-Y'),
|
||||
];
|
||||
}
|
||||
|
||||
return [$out, $defaultYmd];
|
||||
}
|
||||
|
||||
public function normalizeCutoffTime(string $raw, string $default = '09:00'): string
|
||||
{
|
||||
$raw = trim($raw);
|
||||
if ($raw === '') {
|
||||
return $default;
|
||||
}
|
||||
if (preg_match('/^(\\d{1,2}):(\\d{2})$/', $raw, $m)) {
|
||||
$hh = str_pad((string) ((int) $m[1]), 2, '0', STR_PAD_LEFT);
|
||||
return $hh . ':' . $m[2];
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
public function firstSundayOfJune(string $schoolYear): \DateTime
|
||||
{
|
||||
$today = new \DateTime('today');
|
||||
$endYear = null;
|
||||
if (preg_match('/^(\\d{4})\\D(\\d{4})$/', $schoolYear, $m)) {
|
||||
$endYear = (int) $m[2];
|
||||
}
|
||||
if ($endYear === null) {
|
||||
$y = (int) $today->format('Y');
|
||||
$endYear = ((int) $today->format('n') > 6) ? ($y + 1) : $y;
|
||||
}
|
||||
|
||||
$juneFirst = new \DateTime(sprintf('%04d-06-01', $endYear));
|
||||
if ((int) $juneFirst->format('w') !== 0) {
|
||||
$juneFirst->modify('next sunday');
|
||||
}
|
||||
return $juneFirst;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,509 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Parents;
|
||||
|
||||
use App\Models\AttendanceData;
|
||||
use App\Models\AttendanceRecord;
|
||||
use App\Models\ParentAttendanceReport;
|
||||
use App\Models\Student;
|
||||
use App\Models\ClassSection;
|
||||
use App\Services\SemesterRangeService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ParentAttendanceReportService
|
||||
{
|
||||
public function __construct(
|
||||
private ParentConfigService $configService,
|
||||
private ParentAttendanceReportCalendarService $calendarService,
|
||||
private SemesterRangeService $semesterRangeService
|
||||
) {
|
||||
}
|
||||
|
||||
public function formData(int $parentId): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$students = Student::query()
|
||||
->where('parent_id', $parentId)
|
||||
->orderBy('firstname')
|
||||
->orderBy('lastname')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
[$sundays, $defaultDate] = $this->calendarService->computeSundays();
|
||||
|
||||
$today = now()->toDateString();
|
||||
$schoolYear = (string) ($context['school_year'] ?? '');
|
||||
$previewRows = DB::table('parent_attendance_reports as par')
|
||||
->select('par.*', 's.firstname', 's.lastname')
|
||||
->leftJoin('students as s', 's.id', '=', 'par.student_id')
|
||||
->where('par.parent_id', $parentId)
|
||||
->where('par.school_year', $schoolYear)
|
||||
->where('par.report_date', '>=', $today)
|
||||
->orderBy('par.report_date')
|
||||
->orderBy('s.firstname')
|
||||
->orderBy('s.lastname')
|
||||
->get()
|
||||
->map(fn ($row) => (array) $row)
|
||||
->all();
|
||||
|
||||
$cutoffThreshold = $this->calendarService->normalizeCutoffTime(
|
||||
(string) ($context['parent_report_cutoff'] ?? ''),
|
||||
'09:00'
|
||||
);
|
||||
|
||||
return [
|
||||
'students' => $students,
|
||||
'today' => $today,
|
||||
'sundays' => $sundays,
|
||||
'defaultDate' => $defaultDate,
|
||||
'myReports' => $previewRows,
|
||||
'cutoffThreshold' => $cutoffThreshold,
|
||||
];
|
||||
}
|
||||
|
||||
public function submit(int $parentId, array $payload): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$schoolYear = (string) ($context['school_year'] ?? '');
|
||||
$semester = (string) ($context['semester'] ?? '');
|
||||
|
||||
$studentIds = array_values(array_filter(array_map('intval', (array) ($payload['student_ids'] ?? []))));
|
||||
if (empty($studentIds)) {
|
||||
throw new \RuntimeException('At least select one student.');
|
||||
}
|
||||
|
||||
$datesInput = $payload['dates'] ?? ($payload['date'] ?? null);
|
||||
$rawDates = [];
|
||||
if (is_array($datesInput)) {
|
||||
$rawDates = $datesInput;
|
||||
} elseif ($datesInput !== null) {
|
||||
$rawDates = [$datesInput];
|
||||
}
|
||||
$rawDates = array_values(array_unique(array_filter(array_map(static function ($val) {
|
||||
return substr((string) $val, 0, 10);
|
||||
}, $rawDates))));
|
||||
|
||||
if (empty($rawDates)) {
|
||||
throw new \RuntimeException('Please select at least one Sunday date.');
|
||||
}
|
||||
|
||||
$type = (string) ($payload['type'] ?? '');
|
||||
if (!in_array($type, ['absent', 'late', 'early_dismissal'], true)) {
|
||||
throw new \RuntimeException('Invalid report type.');
|
||||
}
|
||||
|
||||
$arrivalTime = $payload['arrival_time'] ?? null;
|
||||
$dismissTime = $payload['dismiss_time'] ?? null;
|
||||
$reason = isset($payload['reason']) ? trim((string) $payload['reason']) : null;
|
||||
|
||||
$todayCheck = new \DateTime('today');
|
||||
$cap = $this->calendarService->firstSundayOfJune($schoolYear);
|
||||
$validDates = [];
|
||||
|
||||
foreach ($rawDates as $entry) {
|
||||
$dt = \DateTime::createFromFormat('Y-m-d', $entry);
|
||||
if (!$dt) {
|
||||
throw new \RuntimeException('Invalid date selected: ' . $entry);
|
||||
}
|
||||
if ((int) $dt->format('w') !== 0) {
|
||||
throw new \RuntimeException('Please select Sunday dates only.');
|
||||
}
|
||||
if ($dt < $todayCheck) {
|
||||
throw new \RuntimeException('Past dates are not allowed. Please select today or a future Sunday.');
|
||||
}
|
||||
if ($dt > $cap) {
|
||||
throw new \RuntimeException('The last available date is the first Sunday of June.');
|
||||
}
|
||||
$validDates[$entry] = $dt;
|
||||
}
|
||||
|
||||
if ($type === 'late' && (!is_string($arrivalTime) || $arrivalTime === '')) {
|
||||
throw new \RuntimeException('Please provide an expected arrival time for late reporting.');
|
||||
}
|
||||
if ($type === 'early_dismissal' && (!is_string($dismissTime) || $dismissTime === '')) {
|
||||
throw new \RuntimeException('Please provide a dismissal time for early dismissal.');
|
||||
}
|
||||
if ($type !== 'early_dismissal') {
|
||||
if ($reason === null || $reason === '') {
|
||||
throw new \RuntimeException('Please provide a reason for absence or late arrival.');
|
||||
}
|
||||
}
|
||||
|
||||
$inserted = 0;
|
||||
$blocked = [];
|
||||
$successDetails = [];
|
||||
|
||||
foreach ($studentIds as $sid) {
|
||||
if ($sid <= 0) {
|
||||
continue;
|
||||
}
|
||||
$sectionRow = DB::table('student_class')
|
||||
->select('class_section_id')
|
||||
->where('student_id', $sid)
|
||||
->where('school_year', $schoolYear)
|
||||
->orderByDesc('updated_at')
|
||||
->first();
|
||||
$classSectionId = $sectionRow->class_section_id ?? null;
|
||||
$student = Student::query()->select('firstname', 'lastname')->find($sid);
|
||||
$studentName = $student ? trim($student->firstname . ' ' . $student->lastname) : ('Student #' . $sid);
|
||||
$classLabel = $this->resolveClassSectionLabel($classSectionId);
|
||||
|
||||
foreach ($validDates as $reportDate => $_dt) {
|
||||
$semesterForDate = $this->semesterRangeService->getSemesterForDate($reportDate) ?: $semester;
|
||||
$saved = false;
|
||||
|
||||
$qb = ParentAttendanceReport::query()
|
||||
->where('student_id', $sid)
|
||||
->where('report_date', $reportDate);
|
||||
|
||||
if ($type === 'early_dismissal') {
|
||||
$existingAL = (clone $qb)
|
||||
->whereIn('type', ['absent', 'late'])
|
||||
->first();
|
||||
if ($existingAL) {
|
||||
$blocked[] = $studentName . ' (' . $reportDate . ')';
|
||||
} else {
|
||||
$existingED = (clone $qb)->where('type', 'early_dismissal')->first();
|
||||
if ($existingED) {
|
||||
$existingED->update([
|
||||
'dismiss_time' => $dismissTime ?: $existingED->dismiss_time,
|
||||
'status' => 'new',
|
||||
]);
|
||||
$saved = true;
|
||||
$inserted++;
|
||||
} else {
|
||||
ParentAttendanceReport::query()->create([
|
||||
'parent_id' => $parentId,
|
||||
'student_id' => $sid,
|
||||
'class_section_id' => $classSectionId,
|
||||
'report_date' => $reportDate,
|
||||
'type' => 'early_dismissal',
|
||||
'dismiss_time' => $dismissTime ?: null,
|
||||
'reason' => $reason ?: null,
|
||||
'semester' => $semesterForDate,
|
||||
'school_year' => $schoolYear,
|
||||
'status' => 'new',
|
||||
]);
|
||||
$saved = true;
|
||||
$inserted++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$existingAny = (clone $qb)
|
||||
->whereIn('type', ['absent', 'late', 'early_dismissal'])
|
||||
->first();
|
||||
if ($existingAny) {
|
||||
$blocked[] = $studentName . ' (' . $reportDate . ')';
|
||||
} else {
|
||||
ParentAttendanceReport::query()->create([
|
||||
'parent_id' => $parentId,
|
||||
'student_id' => $sid,
|
||||
'class_section_id' => $classSectionId,
|
||||
'report_date' => $reportDate,
|
||||
'type' => $type,
|
||||
'arrival_time' => ($type === 'late') ? ($arrivalTime ?: null) : null,
|
||||
'reason' => $reason ?: null,
|
||||
'semester' => $semesterForDate,
|
||||
'school_year' => $schoolYear,
|
||||
'status' => 'new',
|
||||
]);
|
||||
$saved = true;
|
||||
$inserted++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($saved && in_array($type, ['absent', 'late'], true)) {
|
||||
$this->reflectToAttendanceData(
|
||||
studentId: $sid,
|
||||
reportDate: $reportDate,
|
||||
type: $type,
|
||||
semester: $semesterForDate,
|
||||
schoolYear: $schoolYear,
|
||||
classSectionId: $classSectionId,
|
||||
reason: $reason,
|
||||
arrivalTime: $arrivalTime
|
||||
);
|
||||
}
|
||||
|
||||
if ($saved) {
|
||||
$successDetails[] = [
|
||||
'student_id' => $sid,
|
||||
'name' => $studentName,
|
||||
'class_label' => $classLabel,
|
||||
'type' => $type,
|
||||
'arrival_time' => $arrivalTime,
|
||||
'dismiss_time' => $dismissTime,
|
||||
'date' => $reportDate,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($inserted <= 0) {
|
||||
$err = !empty($blocked)
|
||||
? ('Already submitted and cannot be changed for: ' . implode(', ', array_slice($blocked, 0, 5)) . (count($blocked) > 5 ? '…' : ''))
|
||||
: 'Could not save your report. Please try again.';
|
||||
throw new \RuntimeException($err);
|
||||
}
|
||||
|
||||
return [
|
||||
'inserted' => $inserted,
|
||||
'blocked' => $blocked,
|
||||
'students' => $successDetails,
|
||||
'dates' => array_keys($validDates),
|
||||
];
|
||||
}
|
||||
|
||||
public function listReports(string $start, ?string $end, ?string $schoolYear, ?string $semester): array
|
||||
{
|
||||
return ParentAttendanceReport::listForDateRange($start, $end, $schoolYear, $semester);
|
||||
}
|
||||
|
||||
public function checkExisting(array $payload): array
|
||||
{
|
||||
$datesPost = $payload['dates'] ?? null;
|
||||
$dateSingle = $payload['date'] ?? null;
|
||||
$dateValues = [];
|
||||
if (is_array($datesPost)) {
|
||||
foreach ($datesPost as $d) {
|
||||
$dateValues[] = substr((string) $d, 0, 10);
|
||||
}
|
||||
} elseif ($dateSingle !== null) {
|
||||
$dateValues[] = substr((string) $dateSingle, 0, 10);
|
||||
}
|
||||
$dateValues = array_values(array_unique(array_filter($dateValues, static function ($d) {
|
||||
return preg_match('/^\\d{4}-\\d{2}-\\d{2}$/', $d);
|
||||
})));
|
||||
|
||||
$type = (string) ($payload['type'] ?? '');
|
||||
if (empty($dateValues) || !in_array($type, ['absent', 'late', 'early_dismissal'], true)) {
|
||||
throw new \RuntimeException('Invalid date or type.');
|
||||
}
|
||||
|
||||
$studentIds = array_values(array_unique(array_map('intval', (array) ($payload['student_ids'] ?? []))));
|
||||
$studentIds = array_filter($studentIds, static fn ($v) => $v > 0);
|
||||
if (empty($studentIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = DB::table('parent_attendance_reports as par')
|
||||
->select('par.student_id', 'par.type', 'par.report_date', 's.firstname', 's.lastname')
|
||||
->leftJoin('students as s', 's.id', '=', 'par.student_id')
|
||||
->whereIn('par.report_date', $dateValues)
|
||||
->whereIn('par.student_id', $studentIds)
|
||||
->get()
|
||||
->map(fn ($row) => (array) $row)
|
||||
->all();
|
||||
|
||||
$map = [];
|
||||
foreach ($rows as $row) {
|
||||
$sid = (int) ($row['student_id'] ?? 0);
|
||||
if ($sid <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($map[$sid])) {
|
||||
$map[$sid] = [
|
||||
'student_id' => $sid,
|
||||
'firstname' => (string) ($row['firstname'] ?? ''),
|
||||
'lastname' => (string) ($row['lastname'] ?? ''),
|
||||
'dates' => [],
|
||||
];
|
||||
}
|
||||
$dVal = substr((string) ($row['report_date'] ?? ''), 0, 10);
|
||||
if ($dVal === '') {
|
||||
continue;
|
||||
}
|
||||
$map[$sid]['dates'][$dVal] = $map[$sid]['dates'][$dVal] ?? [];
|
||||
$t = (string) ($row['type'] ?? '');
|
||||
if ($t !== '' && !in_array($t, $map[$sid]['dates'][$dVal], true)) {
|
||||
$map[$sid]['dates'][$dVal][] = $t;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($map);
|
||||
}
|
||||
|
||||
public function updateReport(int $parentId, int $reportId, array $payload): void
|
||||
{
|
||||
$row = ParentAttendanceReport::query()->find($reportId);
|
||||
if (!$row || (int) $row->parent_id !== $parentId) {
|
||||
throw new \RuntimeException('Report not found.');
|
||||
}
|
||||
|
||||
if ((string) ($row->status ?? '') !== 'new') {
|
||||
throw new \RuntimeException('This report can no longer be edited.');
|
||||
}
|
||||
|
||||
$tzName = (string) (config('School')->attendance['timezone'] ?? 'UTC');
|
||||
$tz = new \DateTimeZone($tzName ?: 'UTC');
|
||||
$cutoff = new \DateTime($row->report_date . ' 09:00:00', $tz);
|
||||
$now = new \DateTime('now', $tz);
|
||||
if ($now >= $cutoff) {
|
||||
throw new \RuntimeException('Editing window closed at 9:00 AM on the report date.');
|
||||
}
|
||||
|
||||
$type = (string) $row->type;
|
||||
$reason = trim((string) ($payload['reason'] ?? ''));
|
||||
$arrival = (string) ($payload['arrival_time'] ?? '');
|
||||
$dismiss = (string) ($payload['dismiss_time'] ?? '');
|
||||
|
||||
$update = [
|
||||
'reason' => $reason !== '' ? $reason : null,
|
||||
];
|
||||
|
||||
if ($type === 'late' && $arrival !== '') {
|
||||
if (!preg_match('/^\\d{2}:\\d{2}(:\\d{2})?$/', $arrival)) {
|
||||
throw new \RuntimeException('Invalid arrival time format.');
|
||||
}
|
||||
$update['arrival_time'] = $arrival;
|
||||
}
|
||||
if ($type === 'early_dismissal' && $dismiss !== '') {
|
||||
if (!preg_match('/^\\d{2}:\\d{2}(:\\d{2})?$/', $dismiss)) {
|
||||
throw new \RuntimeException('Invalid dismissal time format.');
|
||||
}
|
||||
$update['dismiss_time'] = $dismiss;
|
||||
}
|
||||
|
||||
if (empty($update)) {
|
||||
throw new \RuntimeException('Nothing to update.');
|
||||
}
|
||||
|
||||
$row->update($update);
|
||||
}
|
||||
|
||||
private function resolveClassSectionLabel(?int $classSectionId): string
|
||||
{
|
||||
$cid = (int) ($classSectionId ?? 0);
|
||||
if ($cid <= 0) {
|
||||
return 'Not Assigned';
|
||||
}
|
||||
|
||||
$name = ClassSection::query()
|
||||
->where('class_section_id', $cid)
|
||||
->value('class_section_name');
|
||||
|
||||
return $name ?: 'Not Assigned';
|
||||
}
|
||||
|
||||
private function reflectToAttendanceData(
|
||||
int $studentId,
|
||||
string $reportDate,
|
||||
string $type,
|
||||
string $semester,
|
||||
string $schoolYear,
|
||||
?int $classSectionId,
|
||||
?string $reason = null,
|
||||
?string $arrivalTime = null
|
||||
): void {
|
||||
$status = ($type === 'late') ? 'late' : (($type === 'absent') ? 'absent' : null);
|
||||
if ($status === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$classSectionId) {
|
||||
$row = DB::table('student_class')
|
||||
->select('class_section_id')
|
||||
->where('student_id', $studentId)
|
||||
->where('school_year', $schoolYear)
|
||||
->orderByDesc('updated_at')
|
||||
->first();
|
||||
$classSectionId = (int) ($row->class_section_id ?? 0);
|
||||
}
|
||||
if ($classSectionId <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$existing = AttendanceData::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('class_section_id', $classSectionId)
|
||||
->where('date', $reportDate)
|
||||
->where('semester', $semester)
|
||||
->where('school_year', $schoolYear)
|
||||
->first();
|
||||
|
||||
$reasonParts = ['Parent-reported'];
|
||||
if ($type === 'late' && $arrivalTime) {
|
||||
$reasonParts[] = 'ETA ' . substr($arrivalTime, 0, 5);
|
||||
}
|
||||
if (!empty($reason)) {
|
||||
$reasonParts[] = trim($reason);
|
||||
}
|
||||
$reasonText = implode(' — ', $reasonParts);
|
||||
|
||||
if (!$existing) {
|
||||
$classId = ClassSection::query()
|
||||
->where('class_section_id', $classSectionId)
|
||||
->value('class_id');
|
||||
$schoolId = Student::query()->whereKey($studentId)->value('school_id');
|
||||
if (!$classId || !$schoolId) {
|
||||
return;
|
||||
}
|
||||
AttendanceData::query()->create([
|
||||
'class_id' => $classId,
|
||||
'class_section_id' => $classSectionId,
|
||||
'student_id' => $studentId,
|
||||
'school_id' => $schoolId,
|
||||
'date' => $reportDate,
|
||||
'status' => $status,
|
||||
'reason' => $reasonText,
|
||||
'semester' => $semester,
|
||||
'school_year' => $schoolYear,
|
||||
]);
|
||||
$this->bumpAttendanceRecord($studentId, $classSectionId, $schoolId, $status, $semester, $schoolYear);
|
||||
return;
|
||||
}
|
||||
|
||||
$existingStatus = strtolower((string) ($existing->status ?? ''));
|
||||
if ($existingStatus === 'present' || $existingStatus === '') {
|
||||
$existing->update([
|
||||
'status' => $status,
|
||||
'reason' => $existing->reason ?: $reasonText,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function bumpAttendanceRecord(
|
||||
int $studentId,
|
||||
int $classSectionId,
|
||||
string $schoolId,
|
||||
string $status,
|
||||
string $semester,
|
||||
string $schoolYear
|
||||
): void {
|
||||
$record = AttendanceRecord::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('semester', $semester)
|
||||
->where('school_year', $schoolYear)
|
||||
->first();
|
||||
|
||||
if ($record) {
|
||||
$updates = [
|
||||
'total_presence' => (int) ($record->total_presence ?? 0),
|
||||
'total_absence' => (int) ($record->total_absence ?? 0),
|
||||
'total_late' => (int) ($record->total_late ?? 0),
|
||||
'total_attendance' => (int) ($record->total_attendance ?? 0) + 1,
|
||||
];
|
||||
if ($status === 'present') {
|
||||
$updates['total_presence']++;
|
||||
} elseif ($status === 'absent') {
|
||||
$updates['total_absence']++;
|
||||
} elseif ($status === 'late') {
|
||||
$updates['total_late']++;
|
||||
}
|
||||
$record->update($updates);
|
||||
return;
|
||||
}
|
||||
|
||||
AttendanceRecord::query()->create([
|
||||
'class_section_id' => $classSectionId,
|
||||
'student_id' => $studentId,
|
||||
'school_id' => $schoolId,
|
||||
'semester' => $semester,
|
||||
'school_year' => $schoolYear,
|
||||
'total_presence' => ($status === 'present') ? 1 : 0,
|
||||
'total_absence' => ($status === 'absent') ? 1 : 0,
|
||||
'total_late' => ($status === 'late') ? 1 : 0,
|
||||
'total_attendance' => 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Parents;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ParentAttendanceService
|
||||
{
|
||||
public function __construct(private ParentConfigService $configService)
|
||||
{
|
||||
}
|
||||
|
||||
public function listAttendance(int $parentId, ?string $schoolYear = null): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$selectedYear = $schoolYear ?: $context['school_year'];
|
||||
|
||||
$rows = DB::table('attendance_data')
|
||||
->select('students.firstname', 'students.lastname', 'attendance_data.date', 'attendance_data.status', 'attendance_data.reason')
|
||||
->join('students', 'students.id', '=', 'attendance_data.student_id')
|
||||
->where('students.parent_id', $parentId)
|
||||
->where('attendance_data.school_year', $selectedYear)
|
||||
->orderBy('attendance_data.date', 'desc')
|
||||
->get()
|
||||
->map(fn ($row) => (array) $row)
|
||||
->all();
|
||||
|
||||
$schoolYears = DB::table('attendance_data')
|
||||
->select('school_year')
|
||||
->distinct()
|
||||
->orderBy('school_year', 'desc')
|
||||
->get()
|
||||
->map(fn ($row) => (array) $row)
|
||||
->all();
|
||||
|
||||
return [
|
||||
'attendance' => $rows,
|
||||
'schoolYears' => $schoolYears,
|
||||
'selectedYear' => $selectedYear,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Parents;
|
||||
|
||||
use App\Models\Configuration;
|
||||
|
||||
class ParentConfigService
|
||||
{
|
||||
public function context(): array
|
||||
{
|
||||
return [
|
||||
'date_age_reference' => Configuration::getConfig('date_age_reference'),
|
||||
'enrollment_deadline' => Configuration::getConfig('enrollment_deadline'),
|
||||
'fall_semester_start' => Configuration::getConfig('fall_semester_start'),
|
||||
'refund_deadline' => Configuration::getConfig('refund_deadline'),
|
||||
'school_year' => Configuration::getConfig('school_year'),
|
||||
'semester' => Configuration::getConfig('semester'),
|
||||
'max_kids' => (int) (Configuration::getConfig('max_kids') ?? 0),
|
||||
'max_emergency' => (int) (Configuration::getConfig('max_emergency') ?? 0),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Parents;
|
||||
|
||||
use App\Models\EmergencyContact;
|
||||
use App\Services\PhoneFormatterService;
|
||||
|
||||
class ParentEmergencyContactService
|
||||
{
|
||||
public function __construct(
|
||||
private ParentConfigService $configService,
|
||||
private PhoneFormatterService $phoneFormatter
|
||||
) {
|
||||
}
|
||||
|
||||
public function list(int $parentId): array
|
||||
{
|
||||
return EmergencyContact::query()
|
||||
->where('parent_id', $parentId)
|
||||
->orderBy('emergency_contact_name')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function store(int $parentId, array $payload): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$contact = EmergencyContact::query()->create([
|
||||
'parent_id' => $parentId,
|
||||
'emergency_contact_name' => $payload['name'],
|
||||
'cellphone' => $this->phoneFormatter->formatPhoneNumber($payload['cellphone']),
|
||||
'email' => $payload['email'] ?? null,
|
||||
'relation' => $payload['relation'] ?? null,
|
||||
'semester' => $context['semester'],
|
||||
'school_year' => $context['school_year'],
|
||||
]);
|
||||
|
||||
return $contact->toArray();
|
||||
}
|
||||
|
||||
public function update(int $parentId, int $contactId, array $payload): array
|
||||
{
|
||||
$contact = EmergencyContact::query()
|
||||
->where('id', $contactId)
|
||||
->where('parent_id', $parentId)
|
||||
->firstOrFail();
|
||||
|
||||
$contact->update([
|
||||
'emergency_contact_name' => $payload['name'],
|
||||
'cellphone' => $this->phoneFormatter->formatPhoneNumber($payload['cellphone']),
|
||||
'email' => $payload['email'] ?? null,
|
||||
'relation' => $payload['relation'] ?? null,
|
||||
]);
|
||||
|
||||
return $contact->toArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Parents;
|
||||
|
||||
use App\Models\Enrollment;
|
||||
use App\Models\Refund;
|
||||
use App\Models\Student;
|
||||
use App\Models\StudentClass;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ParentEnrollmentService
|
||||
{
|
||||
public function __construct(private ParentConfigService $configService)
|
||||
{
|
||||
}
|
||||
|
||||
public function overview(int $parentId, ?string $schoolYear = null): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$selectedYear = $schoolYear ?: $context['school_year'];
|
||||
|
||||
$students = Student::query()
|
||||
->where('parent_id', $parentId)
|
||||
->orderBy('lastname')
|
||||
->orderBy('firstname')
|
||||
->get()
|
||||
->map(function ($student) use ($selectedYear) {
|
||||
$classSections = StudentClass::getClassSectionsByStudentId($student->id, $selectedYear, true);
|
||||
$student->class_section = !empty($classSections)
|
||||
? implode(', ', $classSections)
|
||||
: 'Class not Assigned';
|
||||
|
||||
$isArabicClass = !empty($classSections) && array_reduce(
|
||||
$classSections,
|
||||
static fn ($carry, $name) => $carry || (is_string($name) && stripos($name, 'arabic') === 0),
|
||||
false
|
||||
);
|
||||
|
||||
$enrollment = Enrollment::query()
|
||||
->select('enrollment_status', 'admission_status')
|
||||
->where('student_id', $student->id)
|
||||
->where('school_year', $selectedYear)
|
||||
->first();
|
||||
|
||||
$statusMap = [
|
||||
'admission under review' => 'admission under review',
|
||||
'payment pending' => 'payment pending',
|
||||
'enrolled' => 'enrolled',
|
||||
'withdraw under review' => 'withdraw under review',
|
||||
'refund pending' => 'refund pending',
|
||||
'withdrawn' => 'withdrawn',
|
||||
'denied' => 'denied',
|
||||
'waitlist' => 'waitlist',
|
||||
];
|
||||
|
||||
if ($enrollment && isset($enrollment->admission_status)) {
|
||||
$student->admission_status = $enrollment->admission_status;
|
||||
if ($enrollment->admission_status === 'denied') {
|
||||
$student->enrollment_status = 'denied';
|
||||
} else {
|
||||
$student->enrollment_status = $statusMap[$enrollment->enrollment_status] ?? 'not enrolled';
|
||||
}
|
||||
} else {
|
||||
$student->admission_status = null;
|
||||
$student->enrollment_status = 'not enrolled';
|
||||
}
|
||||
|
||||
if ($student->enrollment_status === 'not enrolled' && $isArabicClass) {
|
||||
$student->enrollment_status = 'enrolled';
|
||||
}
|
||||
|
||||
$student->disable_enroll = in_array(
|
||||
$student->enrollment_status,
|
||||
['admission under review', 'payment pending', 'enrolled', 'withdraw under review', 'denied'],
|
||||
true
|
||||
);
|
||||
|
||||
return $student->toArray();
|
||||
})
|
||||
->all();
|
||||
|
||||
$schoolYears = DB::table('enrollments')
|
||||
->select('school_year')
|
||||
->distinct()
|
||||
->orderBy('school_year', 'desc')
|
||||
->get()
|
||||
->map(fn ($row) => (array) $row)
|
||||
->all();
|
||||
|
||||
if (empty($schoolYears)) {
|
||||
$schoolYears[] = ['school_year' => $selectedYear];
|
||||
}
|
||||
|
||||
return [
|
||||
'students' => $students,
|
||||
'schoolYears' => $schoolYears,
|
||||
'selectedYear' => $selectedYear,
|
||||
'withdrawalDeadline' => $context['refund_deadline'],
|
||||
'lastDayOfRegistration' => $context['enrollment_deadline'],
|
||||
'schoolStartDate' => $context['fall_semester_start'],
|
||||
];
|
||||
}
|
||||
|
||||
public function updateEnrollment(int $parentId, array $enrollIds, array $withdrawIds): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$schoolYear = $context['school_year'];
|
||||
$semester = $context['semester'];
|
||||
|
||||
$enrolled = [];
|
||||
$withdrawn = [];
|
||||
|
||||
foreach ($enrollIds as $studentId) {
|
||||
$existing = Enrollment::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('school_year', $schoolYear)
|
||||
->where('semester', $semester)
|
||||
->first();
|
||||
|
||||
if ($existing) {
|
||||
if ((int) $existing->is_withdrawn === 1) {
|
||||
$existing->update([
|
||||
'is_withdrawn' => 0,
|
||||
'withdrawal_date' => null,
|
||||
'enrollment_status' => 'payment pending',
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
Enrollment::query()->create([
|
||||
'student_id' => $studentId,
|
||||
'parent_id' => $parentId,
|
||||
'school_year' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
'enrollment_date' => now()->toDateString(),
|
||||
'is_withdrawn' => 0,
|
||||
'enrollment_status' => 'admission under review',
|
||||
'admission_status' => 'pending',
|
||||
'created_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
$enrolled[] = (int) $studentId;
|
||||
}
|
||||
|
||||
foreach ($withdrawIds as $studentId) {
|
||||
$enrollment = Enrollment::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('school_year', $schoolYear)
|
||||
->where('semester', $semester)
|
||||
->where('is_withdrawn', 0)
|
||||
->first();
|
||||
|
||||
if (!$enrollment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$enrollment->update([
|
||||
'withdrawal_date' => now()->toDateString(),
|
||||
'enrollment_status' => 'withdraw under review',
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$invoice = DB::table('invoices')
|
||||
->where('parent_id', $parentId)
|
||||
->where('school_year', $schoolYear)
|
||||
->orderByDesc('created_at')
|
||||
->first();
|
||||
|
||||
if ($invoice) {
|
||||
$refund = Refund::query()
|
||||
->where('parent_id', $parentId)
|
||||
->where('invoice_id', $invoice->id)
|
||||
->where('school_year', $schoolYear)
|
||||
->first();
|
||||
|
||||
if ($refund) {
|
||||
$refund->update([
|
||||
'reason' => 'Withdrawal under review for student ID ' . $studentId,
|
||||
'note' => null,
|
||||
'updated_by' => $parentId,
|
||||
]);
|
||||
} else {
|
||||
Refund::query()->create([
|
||||
'parent_id' => $parentId,
|
||||
'invoice_id' => $invoice->id,
|
||||
'requested_at' => now(),
|
||||
'school_year' => $schoolYear,
|
||||
'status' => Refund::STATUS_PENDING,
|
||||
'reason' => 'Withdrawal under review for student ID ' . $studentId,
|
||||
'request' => 'new',
|
||||
'semester' => $semester,
|
||||
'refund_paid_amount' => 0.0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$withdrawn[] = (int) $studentId;
|
||||
}
|
||||
|
||||
return [
|
||||
'enrolled' => $enrolled,
|
||||
'withdrawn' => $withdrawn,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Parents;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\Models\EventCharges;
|
||||
use App\Models\Enrollment;
|
||||
use App\Services\Invoices\InvoiceGenerationService;
|
||||
|
||||
class ParentEventParticipationService
|
||||
{
|
||||
public function __construct(
|
||||
private ParentConfigService $configService,
|
||||
private InvoiceGenerationService $invoiceGeneration
|
||||
) {
|
||||
}
|
||||
|
||||
public function overview(int $parentId): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$schoolYear = $context['school_year'];
|
||||
$semester = $context['semester'];
|
||||
|
||||
$activeEvents = Event::getActiveEvents($schoolYear, $semester) ?? [];
|
||||
$chargesList = EventCharges::getChargesWithEventInfo($parentId, $schoolYear) ?? [];
|
||||
|
||||
$charges = [];
|
||||
foreach ($chargesList as $charge) {
|
||||
$key = $charge['student_id'] . ':' . $charge['event_id'];
|
||||
$charges[$key] = [
|
||||
'participation' => $charge['participation'],
|
||||
'date' => $charge['updated_at'] ?? $charge['created_at'],
|
||||
];
|
||||
}
|
||||
|
||||
$students = Enrollment::getEnrolledStudents($parentId, $schoolYear);
|
||||
|
||||
return [
|
||||
'activeEvents' => $activeEvents,
|
||||
'charges' => $charges,
|
||||
'yourStudents' => $students,
|
||||
];
|
||||
}
|
||||
|
||||
public function updateParticipation(int $parentId, array $participations): void
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$schoolYear = $context['school_year'];
|
||||
$semester = $context['semester'];
|
||||
|
||||
foreach ($participations as $key => $value) {
|
||||
[$studentId, $eventId] = array_map('intval', explode(':', (string) $key));
|
||||
$existing = EventCharges::query()
|
||||
->where('parent_id', $parentId)
|
||||
->where('student_id', $studentId)
|
||||
->where('event_id', $eventId)
|
||||
->first();
|
||||
|
||||
if ($value === 'no') {
|
||||
if ($existing) {
|
||||
$existing->delete();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($existing) {
|
||||
$existing->update(['participation' => $value]);
|
||||
} else {
|
||||
$event = Event::getEvent($eventId, $schoolYear);
|
||||
EventCharges::query()->create([
|
||||
'parent_id' => $parentId,
|
||||
'student_id' => $studentId,
|
||||
'event_id' => $eventId,
|
||||
'participation' => $value,
|
||||
'charged' => $event['amount'] ?? 0,
|
||||
'school_year' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
'updated_by' => $parentId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->invoiceGeneration->generateInvoice($parentId, $schoolYear);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Parents;
|
||||
|
||||
use App\Models\Invoice;
|
||||
|
||||
class ParentInvoiceService
|
||||
{
|
||||
public function listInvoices(int $parentId, ?string $schoolYear = null): array
|
||||
{
|
||||
$rows = Invoice::query()
|
||||
->where('parent_id', $parentId)
|
||||
->when($schoolYear, fn ($q) => $q->where('school_year', $schoolYear))
|
||||
->orderByDesc('created_at')
|
||||
->get();
|
||||
|
||||
return $rows->toArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Parents;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
class ParentProfileService
|
||||
{
|
||||
public function getProfile(int $parentId): ?array
|
||||
{
|
||||
$user = User::query()->find($parentId);
|
||||
return $user ? $user->toArray() : null;
|
||||
}
|
||||
|
||||
public function updateProfile(int $parentId, array $payload): array
|
||||
{
|
||||
$user = User::query()->findOrFail($parentId);
|
||||
$user->update([
|
||||
'firstname' => $payload['firstname'],
|
||||
'lastname' => $payload['lastname'],
|
||||
'cellphone' => $payload['cellphone'],
|
||||
'address_street' => $payload['address_street'],
|
||||
'city' => $payload['city'],
|
||||
'state' => $payload['state'],
|
||||
'zip' => $payload['zip'],
|
||||
]);
|
||||
|
||||
return $user->toArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Parents;
|
||||
|
||||
use App\Models\EmergencyContact;
|
||||
use App\Models\Student;
|
||||
use App\Models\StudentAllergy;
|
||||
use App\Models\StudentMedicalCondition;
|
||||
use App\Models\User;
|
||||
use App\Services\SchoolIdService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ParentRegistrationService
|
||||
{
|
||||
public function __construct(
|
||||
private ParentConfigService $configService,
|
||||
private SchoolIdService $schoolIdService
|
||||
) {
|
||||
}
|
||||
|
||||
public function overview(int $parentId): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$parent = User::query()->find($parentId);
|
||||
|
||||
$kids = Student::query()->where('parent_id', $parentId)->get();
|
||||
$kidIds = $kids->pluck('id')->all();
|
||||
|
||||
$allergies = StudentAllergy::query()
|
||||
->whereIn('student_id', $kidIds)
|
||||
->get()
|
||||
->groupBy('student_id');
|
||||
|
||||
$conditions = StudentMedicalCondition::query()
|
||||
->whereIn('student_id', $kidIds)
|
||||
->get()
|
||||
->groupBy('student_id');
|
||||
|
||||
$kids = $kids->map(function ($kid) use ($allergies, $conditions) {
|
||||
$kid->allergies = ($allergies[$kid->id] ?? collect())->pluck('allergy')->all();
|
||||
$kid->medical_conditions = ($conditions[$kid->id] ?? collect())->pluck('condition_name')->all();
|
||||
return $kid->toArray();
|
||||
})->all();
|
||||
|
||||
$emergencies = EmergencyContact::query()->where('parent_id', $parentId)->get()->toArray();
|
||||
|
||||
return [
|
||||
'parent' => $parent ? $parent->toArray() : null,
|
||||
'existingKids' => $kids,
|
||||
'emergencies' => $emergencies,
|
||||
'maxChilds' => $context['max_kids'],
|
||||
'maxEmergency' => $context['max_emergency'],
|
||||
'lastDayOfRegistration' => $context['enrollment_deadline'],
|
||||
'registrationAgeDeadline' => $context['date_age_reference'],
|
||||
];
|
||||
}
|
||||
|
||||
public function register(int $parentId, array $students, array $emergencyContacts): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$schoolYear = $context['school_year'];
|
||||
$semester = $context['semester'];
|
||||
$ageReference = $context['date_age_reference'] ?: now()->toDateString();
|
||||
|
||||
$existingKidsCount = Student::query()->where('parent_id', $parentId)->count();
|
||||
$existingECCount = EmergencyContact::query()->where('parent_id', $parentId)->count();
|
||||
|
||||
if ($existingKidsCount + count($students) > $context['max_kids']) {
|
||||
throw new \RuntimeException('Student limit exceeded.');
|
||||
}
|
||||
|
||||
if ($existingECCount + count($emergencyContacts) > $context['max_emergency']) {
|
||||
throw new \RuntimeException('Emergency contact limit exceeded.');
|
||||
}
|
||||
|
||||
$createdStudentIds = [];
|
||||
|
||||
DB::transaction(function () use (
|
||||
$parentId,
|
||||
$students,
|
||||
$emergencyContacts,
|
||||
$schoolYear,
|
||||
$semester,
|
||||
&$createdStudentIds
|
||||
) {
|
||||
foreach ($students as $student) {
|
||||
$exists = Student::query()
|
||||
->where('school_year', $schoolYear)
|
||||
->where('dob', $student['dob'])
|
||||
->where('firstname', $student['firstname'])
|
||||
->where('lastname', $student['lastname'])
|
||||
->first();
|
||||
|
||||
if ($exists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$row = Student::query()->create([
|
||||
'firstname' => $student['firstname'],
|
||||
'lastname' => $student['lastname'],
|
||||
'dob' => $student['dob'],
|
||||
'age' => $this->calculateAge($student['dob'], $ageReference),
|
||||
'gender' => $student['gender'],
|
||||
'registration_grade' => $student['registration_grade'] ?? null,
|
||||
'photo_consent' => !empty($student['photo_consent']) ? 1 : 0,
|
||||
'parent_id' => $parentId,
|
||||
'year_of_registration' => (string) date('Y'),
|
||||
'school_year' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
'is_new' => isset($student['is_new']) ? (int) (bool) $student['is_new'] : null,
|
||||
'registration_date' => now(),
|
||||
'tuition_paid' => 0,
|
||||
'school_id' => $this->schoolIdService->generateStudentSchoolId(),
|
||||
]);
|
||||
|
||||
$createdStudentIds[] = (int) $row->id;
|
||||
|
||||
foreach (($student['medical_conditions'] ?? []) as $condition) {
|
||||
$condition = trim((string) $condition);
|
||||
if ($condition === '') {
|
||||
continue;
|
||||
}
|
||||
StudentMedicalCondition::query()->create([
|
||||
'student_id' => $row->id,
|
||||
'condition_name' => $condition,
|
||||
]);
|
||||
}
|
||||
|
||||
foreach (($student['allergies'] ?? []) as $allergy) {
|
||||
$allergy = trim((string) $allergy);
|
||||
if ($allergy === '') {
|
||||
continue;
|
||||
}
|
||||
StudentAllergy::query()->create([
|
||||
'student_id' => $row->id,
|
||||
'allergy' => $allergy,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($emergencyContacts as $contact) {
|
||||
EmergencyContact::query()->create([
|
||||
'parent_id' => $parentId,
|
||||
'emergency_contact_name' => $contact['name'],
|
||||
'cellphone' => $contact['cellphone'],
|
||||
'email' => $contact['email'] ?? null,
|
||||
'relation' => $contact['relation'] ?? null,
|
||||
'semester' => $semester,
|
||||
'school_year' => $schoolYear,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
return [
|
||||
'created_student_ids' => $createdStudentIds,
|
||||
];
|
||||
}
|
||||
|
||||
public function updateStudent(int $parentId, int $studentId, array $payload): void
|
||||
{
|
||||
$student = Student::query()
|
||||
->where('id', $studentId)
|
||||
->where('parent_id', $parentId)
|
||||
->firstOrFail();
|
||||
|
||||
$ageReference = $this->configService->context()['date_age_reference'] ?: now()->toDateString();
|
||||
|
||||
$student->update([
|
||||
'firstname' => $payload['firstname'],
|
||||
'lastname' => $payload['lastname'],
|
||||
'dob' => $payload['dob'],
|
||||
'age' => $this->calculateAge($payload['dob'], $ageReference),
|
||||
'gender' => $payload['gender'],
|
||||
'registration_grade' => $payload['registration_grade'] ?? null,
|
||||
'photo_consent' => !empty($payload['photo_consent']) ? 1 : 0,
|
||||
]);
|
||||
|
||||
StudentMedicalCondition::query()->where('student_id', $studentId)->delete();
|
||||
foreach (($payload['medical_conditions'] ?? []) as $condition) {
|
||||
$condition = trim((string) $condition);
|
||||
if ($condition === '') {
|
||||
continue;
|
||||
}
|
||||
StudentMedicalCondition::query()->create([
|
||||
'student_id' => $studentId,
|
||||
'condition_name' => $condition,
|
||||
]);
|
||||
}
|
||||
|
||||
StudentAllergy::query()->where('student_id', $studentId)->delete();
|
||||
foreach (($payload['allergies'] ?? []) as $allergy) {
|
||||
$allergy = trim((string) $allergy);
|
||||
if ($allergy === '') {
|
||||
continue;
|
||||
}
|
||||
StudentAllergy::query()->create([
|
||||
'student_id' => $studentId,
|
||||
'allergy' => $allergy,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteStudent(int $parentId, int $studentId): void
|
||||
{
|
||||
$student = Student::query()
|
||||
->where('id', $studentId)
|
||||
->where('parent_id', $parentId)
|
||||
->firstOrFail();
|
||||
|
||||
$student->delete();
|
||||
|
||||
$remaining = Student::query()->where('parent_id', $parentId)->count();
|
||||
if ($remaining === 0) {
|
||||
EmergencyContact::query()->where('parent_id', $parentId)->delete();
|
||||
}
|
||||
}
|
||||
|
||||
private function calculateAge(string $dob, string $referenceDate): int
|
||||
{
|
||||
try {
|
||||
$dobObj = new \DateTimeImmutable($dob);
|
||||
$refObj = new \DateTimeImmutable($referenceDate);
|
||||
return (int) $dobObj->diff($refObj)->y;
|
||||
} catch (\Throwable $e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Reports;
|
||||
|
||||
use App\Models\Configuration;
|
||||
use App\Models\User;
|
||||
|
||||
class SlipPrinterConfigService
|
||||
{
|
||||
public function context(): array
|
||||
{
|
||||
return [
|
||||
'school_year' => Configuration::getConfig('school_year'),
|
||||
'semester' => Configuration::getConfig('semester'),
|
||||
];
|
||||
}
|
||||
|
||||
public function currentAdminName(?int $userId): string
|
||||
{
|
||||
if ($userId && $userId > 0) {
|
||||
$user = User::query()->select('firstname', 'lastname')->find($userId);
|
||||
if ($user) {
|
||||
$full = trim(($user->firstname ?? '') . ' ' . ($user->lastname ?? ''));
|
||||
if ($full !== '') {
|
||||
return $full;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function printerConfig(): array
|
||||
{
|
||||
$get = function ($keys, $default = null) {
|
||||
$keys = is_array($keys) ? $keys : [$keys];
|
||||
foreach ($keys as $key) {
|
||||
$val = env($key);
|
||||
if ($val !== null && $val !== '') {
|
||||
return $val;
|
||||
}
|
||||
}
|
||||
return $default;
|
||||
};
|
||||
|
||||
return [
|
||||
'chars_per_line' => (int) $get(['printer.chars_per_line', 'PRINTER_CHARS_PER_LINE'], 48),
|
||||
'feed_lines' => (int) $get(['printer.feed_lines', 'PRINTER_FEED_LINES'], 3),
|
||||
'paper' => (string) $get(['printer.paper', 'SLIP_PAPER'], 'card'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Reports;
|
||||
|
||||
class SlipPrinterFormatterService
|
||||
{
|
||||
public function toDbDate(?string $value): ?string
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
if ($value === '') {
|
||||
return null;
|
||||
}
|
||||
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
|
||||
return $value;
|
||||
}
|
||||
if (preg_match('/^\d{1,2}\/\d{1,2}\/\d{4}$/', $value)) {
|
||||
$dt = \DateTime::createFromFormat('!m/d/Y', $value);
|
||||
return $dt ? $dt->format('Y-m-d') : null;
|
||||
}
|
||||
if (preg_match('/^\d{1,2}-\d{1,2}-\d{4}$/', $value)) {
|
||||
$dt = \DateTime::createFromFormat('!m-d-Y', $value);
|
||||
return $dt ? $dt->format('Y-m-d') : null;
|
||||
}
|
||||
$ts = strtotime($value);
|
||||
return $ts ? date('Y-m-d', $ts) : null;
|
||||
}
|
||||
|
||||
public function toDbTime(?string $value): ?string
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
if ($value === '') {
|
||||
return null;
|
||||
}
|
||||
$ts = strtotime($value);
|
||||
return $ts ? date('H:i:s', $ts) : null;
|
||||
}
|
||||
|
||||
public function formatDisplayDate(?string $value): string
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
if ($value === '' || $value === '0000-00-00') {
|
||||
return '';
|
||||
}
|
||||
$ts = strtotime($value);
|
||||
return $ts ? date('m/d/Y', $ts) : '';
|
||||
}
|
||||
|
||||
public function formatDisplayTime(?string $value): string
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
$ts = strtotime($value);
|
||||
if ($ts === false) {
|
||||
$ts = strtotime('today ' . $value);
|
||||
}
|
||||
return $ts ? date('h:i A', $ts) : '';
|
||||
}
|
||||
|
||||
public function fitLine(string $line, int $width): string
|
||||
{
|
||||
$line = (string) $line;
|
||||
if (strlen($line) > $width) {
|
||||
return substr($line, 0, $width);
|
||||
}
|
||||
return $line . str_repeat(' ', $width - strlen($line));
|
||||
}
|
||||
|
||||
public function buildSlipLines(array $data, int $width): array
|
||||
{
|
||||
$lines = [];
|
||||
$lines[] = $this->fitLine('Student Name: ' . (string) ($data['student_name'] ?? ''), $width);
|
||||
$lines[] = $this->fitLine('Date: ' . (string) ($data['date'] ?? '') . ' Time In: ' . (string) ($data['time_in'] ?? ''), $width);
|
||||
$lines[] = $this->fitLine('Grade: ' . (string) ($data['grade'] ?? ''), $width);
|
||||
$lines[] = $this->fitLine('Reason: ' . (string) ($data['reason'] ?? ''), $width);
|
||||
$lines[] = $this->fitLine('Admin: ' . (string) ($data['admin_name'] ?? ''), $width);
|
||||
return $lines;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Reports;
|
||||
|
||||
use Dompdf\Dompdf;
|
||||
use Dompdf\Options;
|
||||
|
||||
class SlipPrinterPdfService
|
||||
{
|
||||
public function __construct(private SlipPrinterConfigService $configService)
|
||||
{
|
||||
}
|
||||
|
||||
public function render(array $data, array $lines, array $options = []): array
|
||||
{
|
||||
$paper = strtolower((string) ($options['paper'] ?? $this->configService->printerConfig()['paper'] ?? 'card'));
|
||||
$fontPt = (float) ($options['font_pt'] ?? env('SLIP_FONT_PT', 13.0));
|
||||
$lineH = (float) ($options['line_h'] ?? env('SLIP_LINE_H', 2.0));
|
||||
$linesCount = (int) ($options['rows'] ?? count($lines));
|
||||
if ($linesCount < 1) {
|
||||
$linesCount = count($lines);
|
||||
}
|
||||
|
||||
$ptPerLine = $fontPt * $lineH;
|
||||
$mmPerPt = 25.4 / 72.0;
|
||||
$contentMm = $ptPerLine * $linesCount * $mmPerPt;
|
||||
$pageMarginsMm = 6.0;
|
||||
$fudgeMm = (float) ($options['fudge_mm'] ?? env('SLIP_FUDGE_MM', 12.0));
|
||||
$dynHeightMm = $contentMm + $pageMarginsMm + $fudgeMm;
|
||||
|
||||
switch ($paper) {
|
||||
case 'cardp':
|
||||
case 'card-portrait':
|
||||
$wMm = 53.98;
|
||||
$hMm = max($dynHeightMm, 40.0);
|
||||
break;
|
||||
case 'receipt80':
|
||||
$wMm = 72.0;
|
||||
$hMm = 90.0;
|
||||
break;
|
||||
case 'receipt58':
|
||||
$wMm = 58.0;
|
||||
$hMm = 80.0;
|
||||
break;
|
||||
case 'card':
|
||||
default:
|
||||
$wMm = 85.60;
|
||||
$hMm = max($dynHeightMm, 40.0);
|
||||
}
|
||||
|
||||
$hOverride = $options['height_mm'] ?? $options['h'] ?? null;
|
||||
if (is_numeric($hOverride)) {
|
||||
$hMm = max(30.0, min(200.0, (float) $hOverride));
|
||||
}
|
||||
|
||||
if ($hMm < $dynHeightMm) {
|
||||
$hMm = $dynHeightMm;
|
||||
}
|
||||
|
||||
$html = view('slips/slip_pdf', [
|
||||
'entry' => $data,
|
||||
'lines' => $lines,
|
||||
'page' => ['w_mm' => $wMm, 'h_mm' => $hMm],
|
||||
]);
|
||||
|
||||
$optionsObj = new Options();
|
||||
$optionsObj->set('isRemoteEnabled', true);
|
||||
$optionsObj->set('defaultFont', 'Courier');
|
||||
|
||||
$dompdf = new Dompdf($optionsObj);
|
||||
$dompdf->loadHtml($html, 'UTF-8');
|
||||
|
||||
$mmToPt = 72 / 25.4;
|
||||
$wPt = $wMm * $mmToPt;
|
||||
$hPt = $hMm * $mmToPt;
|
||||
$dompdf->setPaper([0, 0, $wPt, $hPt]);
|
||||
$dompdf->render();
|
||||
|
||||
$filename = 'LateSlip_' . preg_replace('/[^A-Za-z0-9]+/', '_', (string) ($data['student_name'] ?? 'slip')) . '_' . date('Ymd_His') . '.pdf';
|
||||
|
||||
return [
|
||||
'content' => $dompdf->output(),
|
||||
'filename' => $filename,
|
||||
'width_mm' => $wMm,
|
||||
'height_mm' => $hMm,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Reports;
|
||||
|
||||
use App\Models\LateSlipLog;
|
||||
|
||||
class SlipPrinterService
|
||||
{
|
||||
public function __construct(
|
||||
private SlipPrinterConfigService $configService,
|
||||
private SlipPrinterFormatterService $formatter,
|
||||
private SlipPrinterPdfService $pdfService
|
||||
) {
|
||||
}
|
||||
|
||||
public function print(array $input, ?int $userId): array
|
||||
{
|
||||
$data = $this->buildData($input, $userId);
|
||||
if ($data['student_name'] === '') {
|
||||
return ['ok' => false, 'message' => 'Student name is required.'];
|
||||
}
|
||||
|
||||
$this->logSlip($data, $userId);
|
||||
|
||||
$lines = $this->formatter->buildSlipLines($data, $this->lineWidth());
|
||||
$pdf = $this->pdfService->render($data, $lines, $this->paperOptions($input));
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'data' => $data,
|
||||
'pdf' => $pdf['content'],
|
||||
'filename' => $pdf['filename'],
|
||||
];
|
||||
}
|
||||
|
||||
public function preview(array $input, ?int $userId): array
|
||||
{
|
||||
$data = $this->buildData($input, $userId);
|
||||
$width = $this->lineWidth();
|
||||
$feedLines = $this->configService->printerConfig()['feed_lines'] ?? 3;
|
||||
|
||||
$header = "Al Rahma School at ISGL\nSchool Year: {$data['school_year']}\n\n";
|
||||
$body = implode("\n", $this->formatter->buildSlipLines($data, $width));
|
||||
$footer = str_repeat("\n", (int) $feedLines);
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'text' => $header . $body . $footer,
|
||||
'width' => $width,
|
||||
];
|
||||
}
|
||||
|
||||
public function logs(?string $schoolYear = null, ?string $semester = null): array
|
||||
{
|
||||
$query = LateSlipLog::query();
|
||||
if ($schoolYear !== null && trim($schoolYear) !== '') {
|
||||
$query->where('school_year', trim($schoolYear));
|
||||
}
|
||||
|
||||
$semester = strtolower(trim((string) $semester));
|
||||
if ($semester !== '') {
|
||||
if ($semester === 'fall') {
|
||||
$query->whereIn('semester', ['fall', '1', 1]);
|
||||
} elseif ($semester === 'spring') {
|
||||
$query->whereIn('semester', ['spring', '2', 2]);
|
||||
}
|
||||
}
|
||||
|
||||
$rows = $query->orderByDesc('id')->limit(50)->get()->toArray();
|
||||
|
||||
$out = [];
|
||||
foreach ($rows as $row) {
|
||||
$dispDate = $this->formatter->formatDisplayDate($row['slip_date'] ?? '');
|
||||
if ($dispDate === '') {
|
||||
$dispDate = $this->formatter->formatDisplayDate($row['printed_at'] ?? '');
|
||||
}
|
||||
|
||||
$dispTime = $this->formatter->formatDisplayTime($row['time_in'] ?? '');
|
||||
|
||||
$out[] = [
|
||||
'id' => (int) ($row['id'] ?? 0),
|
||||
'school_year' => (string) ($row['school_year'] ?? ''),
|
||||
'semester' => (string) ($row['semester'] ?? ''),
|
||||
'student_name' => (string) ($row['student_name'] ?? ''),
|
||||
'date' => $dispDate,
|
||||
'time_in' => $dispTime,
|
||||
'grade' => (string) ($row['grade'] ?? ''),
|
||||
'reason' => (string) ($row['reason'] ?? ''),
|
||||
'admin_name' => (string) ($row['admin_name'] ?? ''),
|
||||
'printed_at' => (string) ($row['printed_at'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function reprint(int $id, ?int $userId): array
|
||||
{
|
||||
$row = LateSlipLog::query()->find($id);
|
||||
if (!$row) {
|
||||
return ['ok' => false, 'message' => 'Slip not found.'];
|
||||
}
|
||||
|
||||
$data = [
|
||||
'school_year' => (string) ($row->school_year ?? ''),
|
||||
'student_name' => (string) ($row->student_name ?? ''),
|
||||
'date' => $this->formatter->formatDisplayDate($row->slip_date ?? '') ?: date('m/d/Y'),
|
||||
'time_in' => $this->formatter->formatDisplayTime($row->time_in ?? '') ?: date('h:i A'),
|
||||
'grade' => (string) ($row->grade ?? ''),
|
||||
'reason' => (string) ($row->reason ?? ''),
|
||||
'admin_name' => (string) ($row->admin_name ?? ''),
|
||||
];
|
||||
|
||||
$adminFromDb = $this->configService->currentAdminName($userId);
|
||||
if ($adminFromDb !== '') {
|
||||
$data['admin_name'] = $adminFromDb;
|
||||
}
|
||||
|
||||
$lines = $this->formatter->buildSlipLines($data, $this->lineWidth());
|
||||
$pdf = $this->pdfService->render($data, $lines, []);
|
||||
|
||||
$this->logSlip($data, $userId);
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'data' => $data,
|
||||
'pdf' => $pdf['content'],
|
||||
'filename' => $pdf['filename'],
|
||||
];
|
||||
}
|
||||
|
||||
private function buildData(array $input, ?int $userId): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$schoolYear = trim((string) ($input['school_year'] ?? $context['school_year'] ?? ''));
|
||||
|
||||
$data = [
|
||||
'school_year' => $schoolYear,
|
||||
'student_name' => trim((string) ($input['student_name'] ?? '')),
|
||||
'date' => trim((string) ($input['date'] ?? date('m/d/Y'))),
|
||||
'time_in' => trim((string) ($input['time_in'] ?? date('h:i A'))),
|
||||
'grade' => trim((string) ($input['grade'] ?? '')),
|
||||
'reason' => trim((string) ($input['reason'] ?? '')),
|
||||
'admin_name' => trim((string) ($input['admin_name'] ?? '')),
|
||||
];
|
||||
|
||||
$adminFromDb = $this->configService->currentAdminName($userId);
|
||||
if ($adminFromDb !== '') {
|
||||
$data['admin_name'] = $adminFromDb;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function logSlip(array $data, ?int $userId): void
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$logData = [
|
||||
'school_year' => $data['school_year'],
|
||||
'semester' => (string) ($context['semester'] ?? ''),
|
||||
'student_name' => $data['student_name'],
|
||||
'slip_date' => $this->formatter->toDbDate($data['date']),
|
||||
'time_in' => $this->formatter->toDbTime($data['time_in']),
|
||||
'grade' => $data['grade'],
|
||||
'reason' => $data['reason'],
|
||||
'admin_name' => $data['admin_name'],
|
||||
];
|
||||
|
||||
LateSlipLog::logSlip($logData, $userId);
|
||||
}
|
||||
|
||||
private function lineWidth(): int
|
||||
{
|
||||
$cfg = $this->configService->printerConfig();
|
||||
$width = (int) ($cfg['chars_per_line'] ?? 48);
|
||||
return $width > 0 ? $width : 48;
|
||||
}
|
||||
|
||||
private function paperOptions(array $input): array
|
||||
{
|
||||
return [
|
||||
'paper' => $input['paper'] ?? null,
|
||||
'font_pt' => $input['font_pt'] ?? null,
|
||||
'line_h' => $input['line_h'] ?? null,
|
||||
'rows' => $input['rows'] ?? null,
|
||||
'height_mm' => $input['height_mm'] ?? null,
|
||||
'h' => $input['h'] ?? null,
|
||||
'fudge_mm' => $input['fudge_mm'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\SchoolIds;
|
||||
|
||||
use App\Services\SchoolIdService;
|
||||
|
||||
class SchoolIdAssignmentService
|
||||
{
|
||||
public function __construct(private SchoolIdService $schoolIdService)
|
||||
{
|
||||
}
|
||||
|
||||
public function assignToUser(int $userId): ?string
|
||||
{
|
||||
return $this->schoolIdService->assignSchoolIdToUser($userId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\SchoolIds;
|
||||
|
||||
use App\Services\SchoolIdService;
|
||||
|
||||
class SchoolIdGenerationService
|
||||
{
|
||||
public function __construct(private SchoolIdService $schoolIdService)
|
||||
{
|
||||
}
|
||||
|
||||
public function generateStudentId(): string
|
||||
{
|
||||
return $this->schoolIdService->generateStudentSchoolId();
|
||||
}
|
||||
|
||||
public function generateUserId(): string
|
||||
{
|
||||
return $this->schoolIdService->generateUserSchoolId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Settings;
|
||||
|
||||
use App\Models\Configuration;
|
||||
|
||||
class ConfigurationService
|
||||
{
|
||||
public function list(): array
|
||||
{
|
||||
return Configuration::query()
|
||||
->orderBy('id')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function store(array $payload): Configuration
|
||||
{
|
||||
return Configuration::query()->create([
|
||||
'config_key' => (string) $payload['config_key'],
|
||||
'config_value' => (string) $payload['config_value'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(int $id, array $payload): ?Configuration
|
||||
{
|
||||
$config = Configuration::query()->find($id);
|
||||
if (!$config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$config->update([
|
||||
'config_key' => (string) $payload['config_key'],
|
||||
'config_value' => (string) $payload['config_value'],
|
||||
]);
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
$config = Configuration::query()->find($id);
|
||||
if (!$config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $config->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Students;
|
||||
|
||||
use App\Models\ClassSection;
|
||||
use App\Models\Enrollment;
|
||||
use App\Models\Student;
|
||||
use App\Models\StudentClass;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class StudentAssignmentService
|
||||
{
|
||||
public function __construct(private StudentConfigService $configService)
|
||||
{
|
||||
}
|
||||
|
||||
public function assignClasses(int $studentId, array $classSectionIds, bool $isEventOnly, ?int $userId = null): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$semester = (string) ($context['semester'] ?? '');
|
||||
$schoolYear = (string) ($context['school_year'] ?? '');
|
||||
|
||||
$classSectionIds = $this->normalizeIds($classSectionIds);
|
||||
if ($studentId <= 0 || empty($classSectionIds) || $schoolYear === '') {
|
||||
return ['ok' => false, 'message' => 'Missing required data (student/class section).'];
|
||||
}
|
||||
|
||||
$student = Student::query()->find($studentId);
|
||||
if (!$student) {
|
||||
return ['ok' => false, 'message' => 'Student not found.'];
|
||||
}
|
||||
|
||||
$sections = ClassSection::query()
|
||||
->whereIn('class_section_id', $classSectionIds)
|
||||
->orWhereIn('id', $classSectionIds)
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
if (empty($sections)) {
|
||||
return ['ok' => false, 'message' => 'Class/Section not found.'];
|
||||
}
|
||||
|
||||
$sectionMap = [];
|
||||
foreach ($sections as $section) {
|
||||
$sectionMap[(int) ($section['class_section_id'] ?? 0)] = $section;
|
||||
}
|
||||
|
||||
$missing = array_diff($classSectionIds, array_keys($sectionMap));
|
||||
if (!empty($missing)) {
|
||||
return ['ok' => false, 'message' => 'One or more selected classes do not exist.'];
|
||||
}
|
||||
|
||||
$existing = StudentClass::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('school_year', $schoolYear)
|
||||
->get()
|
||||
->keyBy(fn ($row) => (int) ($row->class_section_id ?? 0));
|
||||
|
||||
$primarySectionId = $classSectionIds[0];
|
||||
$primarySection = $sectionMap[$primarySectionId] ?? reset($sectionMap);
|
||||
$parentClassId = (int) ($primarySection['class_id'] ?? 0);
|
||||
if ($parentClassId <= 0) {
|
||||
$parentClassId = (int) (ClassSection::getClassId($primarySectionId) ?? 0);
|
||||
}
|
||||
|
||||
$attendanceStats = [];
|
||||
$scoreStats = [];
|
||||
|
||||
DB::transaction(function () use (
|
||||
$studentId,
|
||||
$schoolYear,
|
||||
$classSectionIds,
|
||||
$isEventOnly,
|
||||
$userId,
|
||||
$existing,
|
||||
$semester,
|
||||
$primarySectionId,
|
||||
$parentClassId,
|
||||
&$attendanceStats,
|
||||
&$scoreStats
|
||||
): void {
|
||||
foreach ($classSectionIds as $classSectionId) {
|
||||
$eventFlag = $isEventOnly ? 1 : 0;
|
||||
if ($existing->has($classSectionId)) {
|
||||
$eventFlag = (int) ($existing[$classSectionId]->is_event_only ?? 0);
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'student_id' => $studentId,
|
||||
'class_section_id' => $classSectionId,
|
||||
'school_year' => $schoolYear,
|
||||
'is_event_only' => $eventFlag,
|
||||
'updated_by' => $userId,
|
||||
'updated_at' => now(),
|
||||
];
|
||||
|
||||
if ($existing->has($classSectionId)) {
|
||||
$existing[$classSectionId]->fill($payload)->save();
|
||||
} else {
|
||||
StudentClass::query()->create($payload + [
|
||||
'created_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$isEventOnly) {
|
||||
$enrollment = Enrollment::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('school_year', $schoolYear)
|
||||
->when($semester !== '', fn ($q) => $q->where('semester', $semester))
|
||||
->first();
|
||||
|
||||
if ($enrollment) {
|
||||
$enrollment->update([
|
||||
'class_section_id' => $primarySectionId,
|
||||
'enrollment_status' => 'payment pending',
|
||||
'admission_status' => 'accepted',
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
$attendanceStats = $this->updateAttendance(
|
||||
$studentId,
|
||||
$primarySectionId,
|
||||
$parentClassId,
|
||||
$semester,
|
||||
$schoolYear,
|
||||
$userId
|
||||
);
|
||||
|
||||
$scoreStats = $this->updateScores(
|
||||
$studentId,
|
||||
$primarySectionId,
|
||||
$semester,
|
||||
$schoolYear,
|
||||
$userId
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$displayNames = [];
|
||||
foreach ($classSectionIds as $classSectionId) {
|
||||
$section = $sectionMap[$classSectionId] ?? null;
|
||||
if (!$section) {
|
||||
continue;
|
||||
}
|
||||
$name = (string) ($section['class_section_name'] ?? '');
|
||||
if ($name === '') {
|
||||
$name = 'Section #' . $classSectionId;
|
||||
}
|
||||
$displayNames[] = $name;
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'student_id' => $studentId,
|
||||
'class_section_id' => $primarySectionId,
|
||||
'class_section_ids' => $classSectionIds,
|
||||
'class_section_names' => $displayNames,
|
||||
'attendance_updates' => $attendanceStats,
|
||||
'score_updates' => $scoreStats,
|
||||
];
|
||||
}
|
||||
|
||||
public function removeClass(int $studentId, int $classSectionId, ?int $userId = null): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$semester = (string) ($context['semester'] ?? '');
|
||||
$schoolYear = (string) ($context['school_year'] ?? '');
|
||||
|
||||
if ($studentId <= 0 || $classSectionId <= 0 || $schoolYear === '') {
|
||||
return ['ok' => false, 'message' => 'Missing required data (student/class section).'];
|
||||
}
|
||||
|
||||
$row = StudentClass::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('class_section_id', $classSectionId)
|
||||
->where('school_year', $schoolYear)
|
||||
->first();
|
||||
|
||||
if (!$row) {
|
||||
return ['ok' => false, 'message' => 'Assignment not found for this student/class.'];
|
||||
}
|
||||
|
||||
$remainingIds = [];
|
||||
$remainingNames = [];
|
||||
|
||||
DB::transaction(function () use (
|
||||
$studentId,
|
||||
$classSectionId,
|
||||
$schoolYear,
|
||||
$semester,
|
||||
$userId,
|
||||
&$remainingIds,
|
||||
&$remainingNames
|
||||
): void {
|
||||
StudentClass::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('class_section_id', $classSectionId)
|
||||
->where('school_year', $schoolYear)
|
||||
->delete();
|
||||
|
||||
$remainingIds = StudentClass::getClassSectionIdsByStudentId($studentId, $schoolYear);
|
||||
$remainingNames = StudentClass::getClassSectionsByStudentId($studentId, $schoolYear, true);
|
||||
|
||||
$enrollment = Enrollment::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('school_year', $schoolYear)
|
||||
->when($semester !== '', fn ($q) => $q->where('semester', $semester))
|
||||
->first();
|
||||
|
||||
if ($enrollment) {
|
||||
$newClassId = !empty($remainingIds) ? $remainingIds[0] : null;
|
||||
if ((int) $enrollment->class_section_id === $classSectionId || $newClassId !== null) {
|
||||
$enrollment->update([
|
||||
'class_section_id' => $newClassId,
|
||||
'updated_at' => now(),
|
||||
'updated_by' => $userId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'student_id' => $studentId,
|
||||
'removed_class_id' => $classSectionId,
|
||||
'remaining_ids' => $remainingIds,
|
||||
'remaining_names' => $remainingNames,
|
||||
];
|
||||
}
|
||||
|
||||
private function updateAttendance(
|
||||
int $studentId,
|
||||
int $classSectionId,
|
||||
int $classId,
|
||||
string $semester,
|
||||
string $schoolYear,
|
||||
?int $modifiedBy
|
||||
): array {
|
||||
$now = now()->toDateTimeString();
|
||||
|
||||
$attendanceUpdated = DB::table('attendance_data')
|
||||
->where('student_id', $studentId)
|
||||
->where('semester', $semester)
|
||||
->where('school_year', $schoolYear)
|
||||
->update([
|
||||
'class_section_id' => $classSectionId,
|
||||
'class_id' => $classId,
|
||||
'updated_at' => $now,
|
||||
'modified_by' => $modifiedBy,
|
||||
]);
|
||||
|
||||
$recordUpdated = DB::table('attendance_record')
|
||||
->where('student_id', $studentId)
|
||||
->where('semester', $semester)
|
||||
->where('school_year', $schoolYear)
|
||||
->update([
|
||||
'class_section_id' => $classSectionId,
|
||||
'updated_at' => $now,
|
||||
'modified_by' => $modifiedBy,
|
||||
]);
|
||||
|
||||
return [
|
||||
'attendance_data_updated' => $attendanceUpdated,
|
||||
'attendance_record_updated' => $recordUpdated,
|
||||
];
|
||||
}
|
||||
|
||||
private function updateScores(
|
||||
int $studentId,
|
||||
int $classSectionId,
|
||||
string $semester,
|
||||
string $schoolYear,
|
||||
?int $modifiedBy
|
||||
): array {
|
||||
$now = now()->toDateTimeString();
|
||||
$tables = [
|
||||
'homework',
|
||||
'quiz',
|
||||
'project',
|
||||
'participation',
|
||||
'midterm_exam',
|
||||
'final_exam',
|
||||
'final_score',
|
||||
'semester_scores',
|
||||
];
|
||||
|
||||
$results = [];
|
||||
foreach ($tables as $table) {
|
||||
$payload = [
|
||||
'class_section_id' => $classSectionId,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
if ($modifiedBy !== null && Schema::hasColumn($table, 'updated_by')) {
|
||||
$payload['updated_by'] = $modifiedBy;
|
||||
}
|
||||
|
||||
$results[$table . '_updated'] = DB::table($table)
|
||||
->where('student_id', $studentId)
|
||||
->where('semester', $semester)
|
||||
->where('school_year', $schoolYear)
|
||||
->update($payload);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
private function normalizeIds(array $ids): array
|
||||
{
|
||||
$values = array_map('intval', $ids);
|
||||
$values = array_values(array_unique(array_filter($values, static fn ($v) => $v > 0)));
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Students;
|
||||
|
||||
use App\Models\ClassSection;
|
||||
use App\Models\PromotionQueue;
|
||||
use App\Models\StudentClass;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class StudentAutoDistributionService
|
||||
{
|
||||
public function __construct(private StudentConfigService $configService)
|
||||
{
|
||||
}
|
||||
|
||||
public function promotionTotals(?string $schoolYear = null): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$year = $schoolYear ?: (string) ($context['school_year'] ?? '');
|
||||
|
||||
$bases = ClassSection::query()
|
||||
->whereRaw("class_section_name NOT LIKE '%-%'")
|
||||
->orderBy('class_id')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
$wanted = [];
|
||||
foreach ($bases as $row) {
|
||||
$nameRaw = (string) ($row['class_section_name'] ?? '');
|
||||
$name = strtolower($nameRaw);
|
||||
if ($name === 'kg' || $name === 'youth') {
|
||||
$wanted[] = $row;
|
||||
continue;
|
||||
}
|
||||
if (ctype_digit($name)) {
|
||||
$num = (int) $name;
|
||||
if ($num >= 1 && $num <= 9) {
|
||||
$wanted[] = $row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
foreach ($wanted as $row) {
|
||||
$classId = (int) ($row['class_id'] ?? 0);
|
||||
if ($classId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cands = DB::table('promotion_queue as pq')
|
||||
->select('pq.student_id')
|
||||
->join('enrollments as e', function ($join) use ($year) {
|
||||
$join->on('e.student_id', '=', 'pq.student_id')
|
||||
->where('e.school_year', '=', $year);
|
||||
})
|
||||
->where('pq.to_class_id', $classId)
|
||||
->where('pq.school_year_to', $year)
|
||||
->whereIn('pq.status', ['queued', 'assigned', 'applied'])
|
||||
->whereIn('e.enrollment_status', ['payment pending', 'enrolled'])
|
||||
->groupBy('pq.student_id')
|
||||
->get();
|
||||
|
||||
$rows[] = [
|
||||
'class_id' => $classId,
|
||||
'class_section_id' => (int) ($row['class_section_id'] ?? 0),
|
||||
'class_section_name' => (string) ($row['class_section_name'] ?? ''),
|
||||
'total' => $cands->count(),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'year' => $year,
|
||||
'rows' => $rows,
|
||||
];
|
||||
}
|
||||
|
||||
public function autoDistribute(int $classId, int $studentsPerSection, ?string $schoolYear = null, ?int $classSectionId = null, ?int $userId = null): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$year = $schoolYear ?: (string) ($context['school_year'] ?? '');
|
||||
$semester = (string) ($context['semester'] ?? '');
|
||||
|
||||
if ($classId <= 0 && $classSectionId > 0) {
|
||||
$classId = (int) (ClassSection::getClassId($classSectionId) ?? 0);
|
||||
}
|
||||
|
||||
if ($classId <= 0 || $studentsPerSection <= 0) {
|
||||
return ['ok' => false, 'message' => 'Invalid class_id or students_per_section.'];
|
||||
}
|
||||
|
||||
$cands = DB::table('promotion_queue as pq')
|
||||
->select('pq.*', 'students.gender')
|
||||
->join('students', 'students.id', '=', 'pq.student_id')
|
||||
->where('pq.to_class_id', $classId)
|
||||
->where('pq.school_year_to', $year)
|
||||
->whereIn('pq.status', ['queued', 'assigned'])
|
||||
->get()
|
||||
->map(fn ($r) => (array) $r)
|
||||
->all();
|
||||
|
||||
if (empty($cands)) {
|
||||
return ['ok' => false, 'message' => 'No students found in promotion queue for selected class/year.'];
|
||||
}
|
||||
|
||||
$studentIds = array_map(static fn ($r) => (int) $r['student_id'], $cands);
|
||||
$enrolledIds = DB::table('enrollments')
|
||||
->select('student_id')
|
||||
->whereIn('student_id', $studentIds)
|
||||
->where('school_year', $year)
|
||||
->whereIn('enrollment_status', ['payment pending', 'enrolled'])
|
||||
->groupBy('student_id')
|
||||
->pluck('student_id')
|
||||
->map(fn ($v) => (int) $v)
|
||||
->all();
|
||||
|
||||
$cands = array_values(array_filter($cands, static function ($row) use ($enrolledIds) {
|
||||
return in_array((int) $row['student_id'], $enrolledIds, true);
|
||||
}));
|
||||
|
||||
if (empty($cands)) {
|
||||
return ['ok' => false, 'message' => 'No eligible enrolled students found to distribute.'];
|
||||
}
|
||||
|
||||
$sectionsNeeded = (int) ceil(count($cands) / $studentsPerSection);
|
||||
$letters = ClassSection::getLetterSectionsByClassId($classId);
|
||||
|
||||
if (empty($letters)) {
|
||||
return ['ok' => false, 'message' => 'No lettered sections found for the selected class.'];
|
||||
}
|
||||
|
||||
if (count($letters) < $sectionsNeeded) {
|
||||
return ['ok' => false, 'message' => 'Not enough sections available.'];
|
||||
}
|
||||
|
||||
$letters = array_slice($letters, 0, $sectionsNeeded);
|
||||
$buckets = [];
|
||||
foreach ($letters as $idx => $section) {
|
||||
$buckets[$idx] = [
|
||||
'class_section_id' => (int) ($section['class_section_id'] ?? 0),
|
||||
'assigned' => [],
|
||||
'male' => 0,
|
||||
'female' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$males = [];
|
||||
$females = [];
|
||||
foreach ($cands as $row) {
|
||||
$gender = (string) ($row['gender'] ?? '');
|
||||
if (strcasecmp($gender, 'Female') === 0) {
|
||||
$females[] = $row;
|
||||
} else {
|
||||
$males[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
$pickBucket = function (string $gender) use (&$buckets, $studentsPerSection): ?int {
|
||||
$bestIdx = null;
|
||||
$bestCnt = PHP_INT_MAX;
|
||||
foreach ($buckets as $idx => $bucket) {
|
||||
if (count($bucket['assigned']) >= $studentsPerSection) {
|
||||
continue;
|
||||
}
|
||||
$cnt = $gender === 'female' ? $bucket['female'] : $bucket['male'];
|
||||
if ($cnt < $bestCnt) {
|
||||
$bestCnt = $cnt;
|
||||
$bestIdx = $idx;
|
||||
}
|
||||
}
|
||||
return $bestIdx;
|
||||
};
|
||||
|
||||
foreach ($males as $row) {
|
||||
$idx = $pickBucket('male');
|
||||
if ($idx === null) {
|
||||
break;
|
||||
}
|
||||
$buckets[$idx]['assigned'][] = (int) $row['student_id'];
|
||||
$buckets[$idx]['male']++;
|
||||
}
|
||||
|
||||
foreach ($females as $row) {
|
||||
$idx = $pickBucket('female');
|
||||
if ($idx === null) {
|
||||
break;
|
||||
}
|
||||
$buckets[$idx]['assigned'][] = (int) $row['student_id'];
|
||||
$buckets[$idx]['female']++;
|
||||
}
|
||||
|
||||
$promoIdsByStudent = [];
|
||||
foreach ($cands as $row) {
|
||||
$promoIdsByStudent[(int) $row['student_id']] = (int) $row['id'];
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($buckets, $promoIdsByStudent, $year, $semester, $userId): void {
|
||||
foreach ($buckets as $bucket) {
|
||||
$sectionId = (int) $bucket['class_section_id'];
|
||||
foreach ($bucket['assigned'] as $studentId) {
|
||||
if (isset($promoIdsByStudent[$studentId])) {
|
||||
PromotionQueue::query()->where('id', $promoIdsByStudent[$studentId])->update([
|
||||
'to_class_section_id' => $sectionId,
|
||||
'status' => 'assigned',
|
||||
'updated_by' => $userId,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
$exists = StudentClass::query()
|
||||
->where('student_id', $studentId)
|
||||
->where('school_year', $year)
|
||||
->when($semester !== '', fn ($q) => $q->where('semester', $semester))
|
||||
->first();
|
||||
|
||||
$payload = [
|
||||
'student_id' => $studentId,
|
||||
'class_section_id' => $sectionId,
|
||||
'school_year' => $year,
|
||||
'semester' => $semester,
|
||||
'updated_by' => $userId,
|
||||
'updated_at' => now(),
|
||||
];
|
||||
|
||||
if ($exists) {
|
||||
$exists->update($payload);
|
||||
} else {
|
||||
StudentClass::query()->create($payload + ['created_at' => now()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$nameById = [];
|
||||
foreach ($letters as $row) {
|
||||
$nameById[(int) $row['class_section_id']] = (string) ($row['class_section_name'] ?? '');
|
||||
}
|
||||
|
||||
$summary = [];
|
||||
foreach ($buckets as $bucket) {
|
||||
$sectionId = (int) $bucket['class_section_id'];
|
||||
$summary[] = [
|
||||
'class_section_id' => $sectionId,
|
||||
'class_section_name' => $nameById[$sectionId] ?? (string) $sectionId,
|
||||
'total' => count($bucket['assigned']),
|
||||
'male' => $bucket['male'],
|
||||
'female' => $bucket['female'],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'message' => 'Auto distribution completed.',
|
||||
'sections' => $summary,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Students;
|
||||
|
||||
use App\Models\Configuration;
|
||||
|
||||
class StudentConfigService
|
||||
{
|
||||
public function context(): array
|
||||
{
|
||||
return [
|
||||
'school_year' => Configuration::getConfig('school_year'),
|
||||
'semester' => Configuration::getConfig('semester'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Students;
|
||||
|
||||
use App\Models\ClassSection;
|
||||
use App\Models\EmergencyContact;
|
||||
use App\Models\Student;
|
||||
use App\Models\StudentClass;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class StudentDirectoryService
|
||||
{
|
||||
public function __construct(private StudentConfigService $configService)
|
||||
{
|
||||
}
|
||||
|
||||
public function assignmentData(?string $schoolYear = null): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$schoolYear = $schoolYear ?: (string) ($context['school_year'] ?? '');
|
||||
$semester = (string) ($context['semester'] ?? '');
|
||||
|
||||
$yearsRows = DB::table('enrollments')
|
||||
->selectRaw('DISTINCT school_year')
|
||||
->orderByDesc('school_year')
|
||||
->get()
|
||||
->map(fn ($r) => (array) $r)
|
||||
->all();
|
||||
|
||||
$schoolYears = array_values(array_filter(array_map(static function ($r) {
|
||||
return isset($r['school_year']) ? (string) $r['school_year'] : null;
|
||||
}, $yearsRows)));
|
||||
|
||||
$students = Student::query()
|
||||
->select('students.id', 'students.firstname', 'students.lastname', 'students.registration_date', 'students.is_new', 'students.age', 'students.parent_id', 'students.registration_grade')
|
||||
->join('enrollments as e', 'e.student_id', '=', 'students.id')
|
||||
->when($schoolYear !== '', fn ($q) => $q->where('e.school_year', $schoolYear))
|
||||
->groupBy('students.id')
|
||||
->orderBy('students.lastname')
|
||||
->orderBy('students.firstname')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
if (empty($students)) {
|
||||
$students = Student::query()
|
||||
->select('students.id', 'students.firstname', 'students.lastname', 'students.registration_date', 'students.is_new', 'students.age', 'students.parent_id', 'students.registration_grade')
|
||||
->orderBy('students.lastname')
|
||||
->orderBy('students.firstname')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
$studentData = [];
|
||||
foreach ($students as $student) {
|
||||
$studentId = (int) ($student['id'] ?? 0);
|
||||
$sectionNames = StudentClass::getClassSectionsByStudentIdWithFlags($studentId, $schoolYear, true);
|
||||
$sectionIds = StudentClass::getClassSectionIdsByStudentId($studentId, $schoolYear);
|
||||
$sectionDisplay = !empty($sectionNames) ? implode(', ', $sectionNames) : '';
|
||||
|
||||
$parentId = (int) ($student['parent_id'] ?? 0);
|
||||
$emergencyInfo = $parentId > 0
|
||||
? (EmergencyContact::getEmergencyContactByParentId($parentId) ?? [])
|
||||
: [];
|
||||
|
||||
$studentData[] = [
|
||||
'student_id' => $studentId,
|
||||
'name' => trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? '')),
|
||||
'age' => $student['age'] ?? 'N/A',
|
||||
'email' => $emergencyInfo['emergency_contact_name'] ?? '',
|
||||
'phone' => $emergencyInfo['cellphone'] ?? '',
|
||||
'registration_grade' => $student['registration_grade'] ?? 'N/A',
|
||||
'class_section_name' => $sectionDisplay !== '' ? $sectionDisplay : 'No class assigned',
|
||||
'class_section_names' => $sectionNames,
|
||||
'class_section_ids' => $sectionIds,
|
||||
'new_student' => ((int) ($student['is_new'] ?? 0) === 1) ? 'Yes' : 'No',
|
||||
'registration_date' => $student['registration_date'] ?? '',
|
||||
'school_year' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
];
|
||||
}
|
||||
|
||||
$classes = ClassSection::query()
|
||||
->select('id', 'class_section_id', 'class_section_name', 'school_year')
|
||||
->when($schoolYear !== '', fn ($q) => $q->where('school_year', $schoolYear))
|
||||
->orderBy('class_section_name')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
if (empty($classes)) {
|
||||
$classes = ClassSection::query()
|
||||
->select('id', 'class_section_id', 'class_section_name')
|
||||
->orderBy('class_section_name')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
return [
|
||||
'students' => $studentData,
|
||||
'classes' => $classes,
|
||||
'schoolYears' => $schoolYears,
|
||||
'selectedYear' => $schoolYear,
|
||||
'currentYear' => (string) ($context['school_year'] ?? ''),
|
||||
'isCurrentYear' => $schoolYear !== '' && $schoolYear === (string) ($context['school_year'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
public function removedStudents(?string $schoolYear = null): array
|
||||
{
|
||||
$context = $this->configService->context();
|
||||
$schoolYear = $schoolYear ?: (string) ($context['school_year'] ?? '');
|
||||
|
||||
$activeStudents = Student::query()
|
||||
->select('id', 'school_id', 'firstname', 'lastname', 'gender', 'age')
|
||||
->where('is_active', 1)
|
||||
->orderBy('lastname')
|
||||
->orderBy('firstname')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
$removedStudents = Student::query()
|
||||
->select('id', 'school_id', 'firstname', 'lastname', 'gender', 'age')
|
||||
->where('is_active', 0)
|
||||
->orderBy('lastname')
|
||||
->orderBy('firstname')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
$classRows = DB::table('student_class as sc')
|
||||
->select('sc.student_id', 'cs.class_section_name')
|
||||
->join('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
|
||||
->when($schoolYear !== '', fn ($q) => $q->where('sc.school_year', $schoolYear))
|
||||
->get()
|
||||
->map(fn ($r) => (array) $r)
|
||||
->all();
|
||||
|
||||
$classMap = [];
|
||||
foreach ($classRows as $row) {
|
||||
$studentId = (int) ($row['student_id'] ?? 0);
|
||||
$name = trim((string) ($row['class_section_name'] ?? ''));
|
||||
if ($studentId <= 0 || $name === '') {
|
||||
continue;
|
||||
}
|
||||
$classMap[$studentId][] = $name;
|
||||
}
|
||||
|
||||
$attachClasses = static function (array $students) use ($classMap): array {
|
||||
foreach ($students as &$student) {
|
||||
$studentId = (int) ($student['id'] ?? 0);
|
||||
$names = $classMap[$studentId] ?? [];
|
||||
$names = array_values(array_unique(array_filter($names)));
|
||||
$student['class_sections'] = $names;
|
||||
$student['class_section_name'] = !empty($names) ? implode(', ', $names) : 'No class assigned';
|
||||
}
|
||||
unset($student);
|
||||
return $students;
|
||||
};
|
||||
|
||||
return [
|
||||
'active_students' => $attachClasses($activeStudents),
|
||||
'removed_students' => $attachClasses($removedStudents),
|
||||
'school_year' => $schoolYear,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Students;
|
||||
|
||||
use App\Models\Student;
|
||||
use App\Models\StudentAllergy;
|
||||
use App\Models\StudentMedicalCondition;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class StudentProfileService
|
||||
{
|
||||
public function updateStudent(int $studentId, array $payload): array
|
||||
{
|
||||
$student = Student::query()->find($studentId);
|
||||
if (!$student) {
|
||||
return ['ok' => false, 'message' => 'Student not found.'];
|
||||
}
|
||||
|
||||
$studentData = array_filter([
|
||||
'school_id' => $payload['school_id'] ?? null,
|
||||
'firstname' => $payload['firstname'] ?? null,
|
||||
'lastname' => $payload['lastname'] ?? null,
|
||||
'dob' => $payload['dob'] ?? null,
|
||||
'age' => $payload['age'] ?? null,
|
||||
'gender' => $payload['gender'] ?? null,
|
||||
'registration_grade' => $payload['registration_grade'] ?? null,
|
||||
'photo_consent' => $payload['photo_consent'] ?? null,
|
||||
'parent_id' => $payload['parent_id'] ?? null,
|
||||
'registration_date' => $payload['registration_date'] ?? null,
|
||||
'tuition_paid' => $payload['tuition_paid'] ?? null,
|
||||
'year_of_registration' => $payload['year_of_registration'] ?? null,
|
||||
'school_year' => $payload['school_year'] ?? null,
|
||||
'rfid_tag' => $payload['rfid_tag'] ?? null,
|
||||
'semester' => $payload['semester'] ?? null,
|
||||
'is_new' => $payload['is_new'] ?? null,
|
||||
'is_active' => $payload['is_active'] ?? null,
|
||||
], static fn ($value) => $value !== null);
|
||||
|
||||
$conditionsInput = (string) ($payload['medical_conditions'] ?? '');
|
||||
$allergiesInput = (string) ($payload['allergies'] ?? '');
|
||||
$conditions = $this->normalizeHealthList($conditionsInput);
|
||||
$allergies = $this->normalizeHealthList($allergiesInput);
|
||||
$shouldSyncConditions = array_key_exists('medical_conditions', $payload);
|
||||
$shouldSyncAllergies = array_key_exists('allergies', $payload);
|
||||
|
||||
DB::transaction(function () use ($student, $studentData, $conditions, $allergies): void {
|
||||
if (!empty($studentData)) {
|
||||
$student->update($studentData);
|
||||
}
|
||||
|
||||
if ($shouldSyncConditions) {
|
||||
$this->syncHealthList($student->id, StudentMedicalCondition::class, 'condition_name', $conditions);
|
||||
}
|
||||
|
||||
if ($shouldSyncAllergies) {
|
||||
$this->syncHealthList($student->id, StudentAllergy::class, 'allergy', $allergies);
|
||||
}
|
||||
});
|
||||
|
||||
return ['ok' => true, 'message' => 'Student updated successfully.'];
|
||||
}
|
||||
|
||||
private function normalizeHealthList(string $value, int $maxLen = 100): array
|
||||
{
|
||||
$value = trim($value);
|
||||
if ($value === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$parts = preg_split('/[,\n;]+/u', $value, -1, PREG_SPLIT_NO_EMPTY) ?: [];
|
||||
$out = [];
|
||||
foreach ($parts as $part) {
|
||||
$item = trim(preg_replace('/\s+/u', ' ', $part));
|
||||
if ($item === '') {
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^(none|n\/a|na|null|nil|no)$/i', $item)) {
|
||||
continue;
|
||||
}
|
||||
$out[mb_strtolower($item, 'UTF-8')] = mb_substr($item, 0, $maxLen, 'UTF-8');
|
||||
}
|
||||
|
||||
return array_values($out);
|
||||
}
|
||||
|
||||
private function syncHealthList(int $studentId, string $modelClass, string $field, array $newValues): void
|
||||
{
|
||||
$model = new $modelClass();
|
||||
$rows = $model->newQuery()->where('student_id', $studentId)->get(['id', $field])->toArray();
|
||||
|
||||
$current = [];
|
||||
$byId = [];
|
||||
foreach ($rows as $row) {
|
||||
$val = (string) ($row[$field] ?? '');
|
||||
$key = mb_strtolower(trim($val), 'UTF-8');
|
||||
if ($key === '') {
|
||||
continue;
|
||||
}
|
||||
$current[$key] = $val;
|
||||
$byId[$key] = (int) ($row['id'] ?? 0);
|
||||
}
|
||||
|
||||
$incoming = [];
|
||||
foreach ($newValues as $value) {
|
||||
$key = mb_strtolower(trim($value), 'UTF-8');
|
||||
if ($key === '') {
|
||||
continue;
|
||||
}
|
||||
$incoming[$key] = $value;
|
||||
}
|
||||
|
||||
$toDeleteKeys = array_diff(array_keys($current), array_keys($incoming));
|
||||
$toInsertKeys = array_diff(array_keys($incoming), array_keys($current));
|
||||
|
||||
if (!empty($toDeleteKeys)) {
|
||||
$ids = array_map(fn ($key) => $byId[$key], $toDeleteKeys);
|
||||
if (!empty($ids)) {
|
||||
$model->newQuery()->whereIn('id', $ids)->delete();
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($toInsertKeys)) {
|
||||
$batch = [];
|
||||
foreach ($toInsertKeys as $key) {
|
||||
$batch[] = [
|
||||
'student_id' => $studentId,
|
||||
$field => $incoming[$key],
|
||||
];
|
||||
}
|
||||
if (!empty($batch)) {
|
||||
$model->newQuery()->insert($batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user