feat: update student import and graduation workflows
This commit is contained in:
@@ -16,7 +16,17 @@ class StudentSyncController extends Controller
|
||||
|
||||
abort_unless(collect($token->abilities ?? [])->contains('students'), 403, 'Token tidak memiliki akses ke data siswa.');
|
||||
|
||||
$classLevel = strtoupper(trim((string) $request->query('class_level', '')));
|
||||
|
||||
$items = Student::query()
|
||||
->when(in_array($classLevel, ['X', 'XI', 'XII'], true), function ($query) use ($classLevel) {
|
||||
$query->whereHas('classRoom', fn ($classQuery) => $classQuery->where('level', $classLevel));
|
||||
})
|
||||
->with([
|
||||
'academicYear:id,name,start_year,end_year,is_active',
|
||||
'department:id,code,name,description,is_active',
|
||||
'classRoom:id,academic_year_id,department_id,name,level,is_active',
|
||||
])
|
||||
->orderBy('full_name')
|
||||
->get([
|
||||
'id',
|
||||
@@ -24,16 +34,74 @@ class StudentSyncController extends Controller
|
||||
'nisn',
|
||||
'full_name',
|
||||
'gender',
|
||||
'place_of_birth',
|
||||
'date_of_birth',
|
||||
'nationality',
|
||||
'address',
|
||||
'postal_code',
|
||||
'academic_year_id',
|
||||
'department_id',
|
||||
'class_id',
|
||||
'entry_date',
|
||||
'status',
|
||||
'phone_number',
|
||||
'email',
|
||||
'photo_path',
|
||||
'notes',
|
||||
'updated_at',
|
||||
]);
|
||||
])
|
||||
->map(function (Student $student) {
|
||||
return [
|
||||
'id' => $student->id,
|
||||
'student_number' => $student->student_number,
|
||||
'nisn' => $student->nisn,
|
||||
'full_name' => $student->full_name,
|
||||
'gender' => $student->gender,
|
||||
'place_of_birth' => $student->place_of_birth,
|
||||
'date_of_birth' => $student->date_of_birth?->toDateString(),
|
||||
'nationality' => $student->nationality,
|
||||
'address' => $student->address,
|
||||
'postal_code' => $student->postal_code,
|
||||
'academic_year_id' => $student->academic_year_id,
|
||||
'academic_year_name' => $student->academicYear?->name,
|
||||
'academic_year' => $student->academicYear ? [
|
||||
'id' => $student->academicYear->id,
|
||||
'name' => $student->academicYear->name,
|
||||
'start_year' => $student->academicYear->start_year,
|
||||
'end_year' => $student->academicYear->end_year,
|
||||
'is_active' => (bool) $student->academicYear->is_active,
|
||||
] : null,
|
||||
'department_id' => $student->department_id,
|
||||
'department_code' => $student->department?->code,
|
||||
'department_name' => $student->department?->name,
|
||||
'department' => $student->department ? [
|
||||
'id' => $student->department->id,
|
||||
'code' => $student->department->code,
|
||||
'name' => $student->department->name,
|
||||
'description' => $student->department->description,
|
||||
'is_active' => (bool) $student->department->is_active,
|
||||
] : null,
|
||||
'class_id' => $student->class_id,
|
||||
'class_name' => $student->classRoom?->name,
|
||||
'class_level' => $student->classRoom?->level,
|
||||
'classroom' => $student->classRoom ? [
|
||||
'id' => $student->classRoom->id,
|
||||
'name' => $student->classRoom->name,
|
||||
'level' => $student->classRoom->level,
|
||||
'academic_year_id' => $student->classRoom->academic_year_id,
|
||||
'department_id' => $student->classRoom->department_id,
|
||||
'is_active' => (bool) $student->classRoom->is_active,
|
||||
] : null,
|
||||
'entry_date' => $student->entry_date?->toDateString(),
|
||||
'status' => $student->status,
|
||||
'phone_number' => $student->phone_number,
|
||||
'email' => $student->email,
|
||||
'photo_path' => $student->photo_path,
|
||||
'notes' => $student->notes,
|
||||
'updated_at' => $student->updated_at?->toISOString(),
|
||||
];
|
||||
})
|
||||
->values();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
|
||||
@@ -21,6 +21,7 @@ class GraduatedStudentController extends Controller
|
||||
$academicYearId = $request->get('academic_year_id');
|
||||
$departmentId = $request->get('department_id');
|
||||
$classId = $request->get('class_id');
|
||||
$documentStatus = (string) $request->get('document_status', 'all');
|
||||
$sort = (string) $request->get('sort', 'full_name');
|
||||
$direction = strtolower((string) $request->get('direction', 'asc')) === 'desc' ? 'desc' : 'asc';
|
||||
|
||||
@@ -38,6 +39,14 @@ class GraduatedStudentController extends Controller
|
||||
$sort = 'full_name';
|
||||
}
|
||||
|
||||
$documentFields = [
|
||||
'skl_file_path',
|
||||
'ijazah_file_path',
|
||||
'transcript_file_path',
|
||||
'ukk_certificate_file_path',
|
||||
'lsp_certificate_file_path',
|
||||
];
|
||||
|
||||
$graduations = StudentGraduation::query()
|
||||
->with(['academicYear', 'department', 'classRoom'])
|
||||
->when($search !== '', function ($query) use ($search) {
|
||||
@@ -50,17 +59,46 @@ class GraduatedStudentController extends Controller
|
||||
->when($academicYearId, fn ($query) => $query->where('academic_year_id', $academicYearId))
|
||||
->when($departmentId, fn ($query) => $query->where('department_id', $departmentId))
|
||||
->when($classId, fn ($query) => $query->where('class_id', $classId))
|
||||
->when($documentStatus === 'incomplete', function ($query) use ($documentFields) {
|
||||
foreach ($documentFields as $field) {
|
||||
$query->orWhereNull($field);
|
||||
}
|
||||
})
|
||||
->when($documentStatus === 'complete', function ($query) use ($documentFields) {
|
||||
foreach ($documentFields as $field) {
|
||||
$query->whereNotNull($field);
|
||||
}
|
||||
})
|
||||
->orderBy($allowedSorts[$sort], $direction)
|
||||
->orderBy('id', $direction)
|
||||
->paginate(10)
|
||||
->withQueryString();
|
||||
|
||||
$totalGraduations = StudentGraduation::count();
|
||||
$summary = [
|
||||
'total' => $graduations->total(),
|
||||
'total' => $totalGraduations,
|
||||
'filtered' => $graduations->total(),
|
||||
'with_skl' => StudentGraduation::query()->whereNotNull('skl_file_path')->count(),
|
||||
'with_ijazah' => StudentGraduation::query()->whereNotNull('ijazah_file_path')->count(),
|
||||
'with_transcript' => StudentGraduation::query()->whereNotNull('transcript_file_path')->count(),
|
||||
'with_ukk_certificate' => StudentGraduation::query()->whereNotNull('ukk_certificate_file_path')->count(),
|
||||
'with_lsp_certificate' => StudentGraduation::query()->whereNotNull('lsp_certificate_file_path')->count(),
|
||||
'this_year' => StudentGraduation::query()->whereYear('graduation_date', now()->year)->count(),
|
||||
'latest_graduation_date' => StudentGraduation::query()->max('graduation_date'),
|
||||
];
|
||||
|
||||
$graduations->getCollection()->transform(function (StudentGraduation $graduation) use ($documentFields) {
|
||||
$availableDocuments = collect($documentFields)->filter(fn ($field) => filled($graduation->{$field}))->count();
|
||||
$graduation->documents_total = count($documentFields);
|
||||
$graduation->documents_available = $availableDocuments;
|
||||
$graduation->documents_missing = max($graduation->documents_total - $availableDocuments, 0);
|
||||
$graduation->documents_progress_percentage = $graduation->documents_total > 0
|
||||
? (int) round(($availableDocuments / $graduation->documents_total) * 100)
|
||||
: 100;
|
||||
|
||||
return $graduation;
|
||||
});
|
||||
|
||||
return view('graduated-students.index', [
|
||||
'graduations' => $graduations,
|
||||
'search' => $search,
|
||||
@@ -68,10 +106,16 @@ class GraduatedStudentController extends Controller
|
||||
'academic_year_id' => $academicYearId,
|
||||
'department_id' => $departmentId,
|
||||
'class_id' => $classId,
|
||||
'document_status' => $documentStatus,
|
||||
],
|
||||
'academicYears' => AcademicYear::orderBy('name')->pluck('name', 'id'),
|
||||
'departments' => Department::orderBy('name')->pluck('name', 'id'),
|
||||
'classes' => SchoolClass::orderBy('name')->pluck('name', 'id'),
|
||||
'documentStatusOptions' => [
|
||||
'all' => 'Semua Dokumen',
|
||||
'complete' => 'Dokumen Lengkap',
|
||||
'incomplete' => 'Dokumen Belum Lengkap',
|
||||
],
|
||||
'sort' => $sort,
|
||||
'direction' => $direction,
|
||||
'summary' => $summary,
|
||||
@@ -94,11 +138,20 @@ class GraduatedStudentController extends Controller
|
||||
'graduation_note' => ['nullable', 'string'],
|
||||
'skl_file' => ['nullable', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:4096'],
|
||||
'ijazah_file' => ['nullable', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:4096'],
|
||||
'transcript_file' => ['nullable', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:4096'],
|
||||
'ukk_certificate_file' => ['nullable', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:4096'],
|
||||
'lsp_certificate_file' => ['nullable', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:4096'],
|
||||
], [
|
||||
'skl_file.mimes' => 'File SKL harus berupa PDF, JPG, JPEG, atau PNG.',
|
||||
'skl_file.max' => 'Ukuran file SKL maksimal 4 MB.',
|
||||
'ijazah_file.mimes' => 'File ijazah harus berupa PDF, JPG, JPEG, atau PNG.',
|
||||
'ijazah_file.max' => 'Ukuran file ijazah maksimal 4 MB.',
|
||||
'transcript_file.mimes' => 'File transkrip nilai harus berupa PDF, JPG, JPEG, atau PNG.',
|
||||
'transcript_file.max' => 'Ukuran file transkrip nilai maksimal 4 MB.',
|
||||
'ukk_certificate_file.mimes' => 'File sertifikat UKK harus berupa PDF, JPG, JPEG, atau PNG.',
|
||||
'ukk_certificate_file.max' => 'Ukuran file sertifikat UKK maksimal 4 MB.',
|
||||
'lsp_certificate_file.mimes' => 'File sertifikat LSP harus berupa PDF, JPG, JPEG, atau PNG.',
|
||||
'lsp_certificate_file.max' => 'Ukuran file sertifikat LSP maksimal 4 MB.',
|
||||
]);
|
||||
|
||||
$graduatedStudent->graduation_date = $validated['graduation_date'] ?? $graduatedStudent->graduation_date;
|
||||
@@ -132,6 +185,48 @@ class GraduatedStudentController extends Controller
|
||||
$graduatedStudent->ijazah_file_size = $file->getSize();
|
||||
}
|
||||
|
||||
if ($request->hasFile('transcript_file')) {
|
||||
if ($graduatedStudent->transcript_file_path && Storage::disk('public')->exists($graduatedStudent->transcript_file_path)) {
|
||||
Storage::disk('public')->delete($graduatedStudent->transcript_file_path);
|
||||
}
|
||||
|
||||
$file = $request->file('transcript_file');
|
||||
$path = $file->store("student-graduation-documents/{$graduatedStudent->student_id}/transcript", 'public');
|
||||
|
||||
$graduatedStudent->transcript_file_name = $file->getClientOriginalName();
|
||||
$graduatedStudent->transcript_file_path = $path;
|
||||
$graduatedStudent->transcript_mime_type = $file->getMimeType();
|
||||
$graduatedStudent->transcript_file_size = $file->getSize();
|
||||
}
|
||||
|
||||
if ($request->hasFile('ukk_certificate_file')) {
|
||||
if ($graduatedStudent->ukk_certificate_file_path && Storage::disk('public')->exists($graduatedStudent->ukk_certificate_file_path)) {
|
||||
Storage::disk('public')->delete($graduatedStudent->ukk_certificate_file_path);
|
||||
}
|
||||
|
||||
$file = $request->file('ukk_certificate_file');
|
||||
$path = $file->store("student-graduation-documents/{$graduatedStudent->student_id}/ukk-certificate", 'public');
|
||||
|
||||
$graduatedStudent->ukk_certificate_file_name = $file->getClientOriginalName();
|
||||
$graduatedStudent->ukk_certificate_file_path = $path;
|
||||
$graduatedStudent->ukk_certificate_mime_type = $file->getMimeType();
|
||||
$graduatedStudent->ukk_certificate_file_size = $file->getSize();
|
||||
}
|
||||
|
||||
if ($request->hasFile('lsp_certificate_file')) {
|
||||
if ($graduatedStudent->lsp_certificate_file_path && Storage::disk('public')->exists($graduatedStudent->lsp_certificate_file_path)) {
|
||||
Storage::disk('public')->delete($graduatedStudent->lsp_certificate_file_path);
|
||||
}
|
||||
|
||||
$file = $request->file('lsp_certificate_file');
|
||||
$path = $file->store("student-graduation-documents/{$graduatedStudent->student_id}/lsp-certificate", 'public');
|
||||
|
||||
$graduatedStudent->lsp_certificate_file_name = $file->getClientOriginalName();
|
||||
$graduatedStudent->lsp_certificate_file_path = $path;
|
||||
$graduatedStudent->lsp_certificate_mime_type = $file->getMimeType();
|
||||
$graduatedStudent->lsp_certificate_file_size = $file->getSize();
|
||||
}
|
||||
|
||||
$graduatedStudent->save();
|
||||
|
||||
Alert::success('Berhasil', 'Arsip siswa lulus berhasil diperbarui.');
|
||||
@@ -141,11 +236,19 @@ class GraduatedStudentController extends Controller
|
||||
|
||||
public function previewDocument(StudentGraduation $graduatedStudent, string $document): View
|
||||
{
|
||||
abort_unless(in_array($document, ['skl', 'ijazah'], true), 404);
|
||||
abort_unless(in_array($document, ['skl', 'ijazah', 'transcript', 'ukk-certificate', 'lsp-certificate'], true), 404);
|
||||
|
||||
$filePathField = $document === 'skl' ? 'skl_file_path' : 'ijazah_file_path';
|
||||
$fileNameField = $document === 'skl' ? 'skl_file_name' : 'ijazah_file_name';
|
||||
$mimeTypeField = $document === 'skl' ? 'skl_mime_type' : 'ijazah_mime_type';
|
||||
$documentConfig = [
|
||||
'skl' => ['path' => 'skl_file_path', 'name' => 'skl_file_name', 'mime' => 'skl_mime_type', 'label' => 'SKL'],
|
||||
'ijazah' => ['path' => 'ijazah_file_path', 'name' => 'ijazah_file_name', 'mime' => 'ijazah_mime_type', 'label' => 'Ijazah'],
|
||||
'transcript' => ['path' => 'transcript_file_path', 'name' => 'transcript_file_name', 'mime' => 'transcript_mime_type', 'label' => 'Transkrip Nilai'],
|
||||
'ukk-certificate' => ['path' => 'ukk_certificate_file_path', 'name' => 'ukk_certificate_file_name', 'mime' => 'ukk_certificate_mime_type', 'label' => 'Sertifikat UKK'],
|
||||
'lsp-certificate' => ['path' => 'lsp_certificate_file_path', 'name' => 'lsp_certificate_file_name', 'mime' => 'lsp_certificate_mime_type', 'label' => 'Sertifikat LSP'],
|
||||
][$document];
|
||||
|
||||
$filePathField = $documentConfig['path'];
|
||||
$fileNameField = $documentConfig['name'];
|
||||
$mimeTypeField = $documentConfig['mime'];
|
||||
|
||||
abort_unless($graduatedStudent->{$filePathField} && Storage::disk('public')->exists($graduatedStudent->{$filePathField}), 404);
|
||||
|
||||
@@ -157,7 +260,7 @@ class GraduatedStudentController extends Controller
|
||||
return view('graduated-students.preview-document', [
|
||||
'graduation' => $graduatedStudent,
|
||||
'documentType' => $document,
|
||||
'documentLabel' => $document === 'skl' ? 'SKL' : 'Ijazah',
|
||||
'documentLabel' => $documentConfig['label'],
|
||||
'fileName' => $graduatedStudent->{$fileNameField},
|
||||
'fileUrl' => $fileUrl,
|
||||
'mimeType' => $mimeType,
|
||||
|
||||
@@ -20,6 +20,9 @@ class GraduationBulkUploadController extends Controller
|
||||
'documentTypes' => [
|
||||
'skl' => 'SKL',
|
||||
'ijazah' => 'Ijazah',
|
||||
'transcript' => 'Transkrip Nilai',
|
||||
'ukk-certificate' => 'Sertifikat UKK',
|
||||
'lsp-certificate' => 'Sertifikat LSP',
|
||||
],
|
||||
'matchByOptions' => [
|
||||
'student_number' => 'NIS',
|
||||
@@ -31,15 +34,15 @@ class GraduationBulkUploadController extends Controller
|
||||
public function preview(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'document_type' => ['required', 'in:skl,ijazah'],
|
||||
'document_type' => ['required', 'in:skl,ijazah,transcript,ukk-certificate,lsp-certificate'],
|
||||
'match_by' => ['required', 'in:student_number,nisn'],
|
||||
'documents' => ['required', 'array', 'min:1', 'max:10'],
|
||||
'documents.*' => ['required', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:2048'],
|
||||
'documents.*' => ['required', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:4096'],
|
||||
], [
|
||||
'documents.required' => 'Pilih minimal satu file dokumen untuk dipreview.',
|
||||
'documents.max' => 'Maksimal 10 file per batch upload.',
|
||||
'documents.*.mimes' => 'Dokumen hanya boleh berupa PDF, JPG, JPEG, atau PNG.',
|
||||
'documents.*.max' => 'Ukuran tiap dokumen maksimal 2 MB.',
|
||||
'documents.*.max' => 'Ukuran tiap dokumen maksimal 4 MB.',
|
||||
]);
|
||||
|
||||
$graduations = StudentGraduation::query()->get()->keyBy($validated['match_by']);
|
||||
@@ -59,7 +62,13 @@ class GraduationBulkUploadController extends Controller
|
||||
$baseName = pathinfo($originalName, PATHINFO_FILENAME);
|
||||
$normalizedKey = trim($baseName);
|
||||
$graduation = $graduations->get($normalizedKey);
|
||||
$existingPathField = $validated['document_type'] === 'skl' ? 'skl_file_path' : 'ijazah_file_path';
|
||||
$existingPathField = [
|
||||
'skl' => 'skl_file_path',
|
||||
'ijazah' => 'ijazah_file_path',
|
||||
'transcript' => 'transcript_file_path',
|
||||
'ukk-certificate' => 'ukk_certificate_file_path',
|
||||
'lsp-certificate' => 'lsp_certificate_file_path',
|
||||
][$validated['document_type']];
|
||||
|
||||
$storedTempPath = $file->store('temp/graduation-bulk-upload', 'public');
|
||||
|
||||
@@ -93,7 +102,13 @@ class GraduationBulkUploadController extends Controller
|
||||
$preview = [
|
||||
'token' => (string) Str::uuid(),
|
||||
'document_type' => $validated['document_type'],
|
||||
'document_label' => $validated['document_type'] === 'skl' ? 'SKL' : 'Ijazah',
|
||||
'document_label' => [
|
||||
'skl' => 'SKL',
|
||||
'ijazah' => 'Ijazah',
|
||||
'transcript' => 'Transkrip Nilai',
|
||||
'ukk-certificate' => 'Sertifikat UKK',
|
||||
'lsp-certificate' => 'Sertifikat LSP',
|
||||
][$validated['document_type']],
|
||||
'match_by' => $validated['match_by'],
|
||||
'match_by_label' => $validated['match_by'] === 'student_number' ? 'NIS' : 'NISN',
|
||||
'rows' => $rows,
|
||||
@@ -120,10 +135,18 @@ class GraduationBulkUploadController extends Controller
|
||||
}
|
||||
|
||||
$documentType = $preview['document_type'];
|
||||
$fileNameField = $documentType === 'skl' ? 'skl_file_name' : 'ijazah_file_name';
|
||||
$filePathField = $documentType === 'skl' ? 'skl_file_path' : 'ijazah_file_path';
|
||||
$mimeTypeField = $documentType === 'skl' ? 'skl_mime_type' : 'ijazah_mime_type';
|
||||
$fileSizeField = $documentType === 'skl' ? 'skl_file_size' : 'ijazah_file_size';
|
||||
$documentConfig = [
|
||||
'skl' => ['name' => 'skl_file_name', 'path' => 'skl_file_path', 'mime' => 'skl_mime_type', 'size' => 'skl_file_size'],
|
||||
'ijazah' => ['name' => 'ijazah_file_name', 'path' => 'ijazah_file_path', 'mime' => 'ijazah_mime_type', 'size' => 'ijazah_file_size'],
|
||||
'transcript' => ['name' => 'transcript_file_name', 'path' => 'transcript_file_path', 'mime' => 'transcript_mime_type', 'size' => 'transcript_file_size'],
|
||||
'ukk-certificate' => ['name' => 'ukk_certificate_file_name', 'path' => 'ukk_certificate_file_path', 'mime' => 'ukk_certificate_mime_type', 'size' => 'ukk_certificate_file_size'],
|
||||
'lsp-certificate' => ['name' => 'lsp_certificate_file_name', 'path' => 'lsp_certificate_file_path', 'mime' => 'lsp_certificate_mime_type', 'size' => 'lsp_certificate_file_size'],
|
||||
][$documentType];
|
||||
|
||||
$fileNameField = $documentConfig['name'];
|
||||
$filePathField = $documentConfig['path'];
|
||||
$mimeTypeField = $documentConfig['mime'];
|
||||
$fileSizeField = $documentConfig['size'];
|
||||
|
||||
$processed = 0;
|
||||
$skipped = 0;
|
||||
|
||||
@@ -144,12 +144,20 @@ class PublicStudentLookupController extends Controller
|
||||
public function downloadGraduationDocument(Student $student, string $document): StreamedResponse
|
||||
{
|
||||
abort_unless($student->status === 'graduated', 404);
|
||||
abort_unless(in_array($document, ['skl', 'ijazah'], true), 404);
|
||||
abort_unless(in_array($document, ['skl', 'ijazah', 'transcript', 'ukk-certificate', 'lsp-certificate'], true), 404);
|
||||
|
||||
$graduation = StudentGraduation::query()->where('student_id', $student->id)->firstOrFail();
|
||||
|
||||
$filePathField = $document === 'skl' ? 'skl_file_path' : 'ijazah_file_path';
|
||||
$fileNameField = $document === 'skl' ? 'skl_file_name' : 'ijazah_file_name';
|
||||
$documentConfig = [
|
||||
'skl' => ['path' => 'skl_file_path', 'name' => 'skl_file_name'],
|
||||
'ijazah' => ['path' => 'ijazah_file_path', 'name' => 'ijazah_file_name'],
|
||||
'transcript' => ['path' => 'transcript_file_path', 'name' => 'transcript_file_name'],
|
||||
'ukk-certificate' => ['path' => 'ukk_certificate_file_path', 'name' => 'ukk_certificate_file_name'],
|
||||
'lsp-certificate' => ['path' => 'lsp_certificate_file_path', 'name' => 'lsp_certificate_file_name'],
|
||||
][$document];
|
||||
|
||||
$filePathField = $documentConfig['path'];
|
||||
$fileNameField = $documentConfig['name'];
|
||||
|
||||
abort_unless($graduation->{$filePathField} && Storage::disk('public')->exists($graduation->{$filePathField}), 404);
|
||||
|
||||
|
||||
@@ -125,6 +125,7 @@ class PublicStudentSelfServiceController extends Controller
|
||||
'date_of_birth' => ['nullable', 'date'],
|
||||
'religion' => ['nullable', 'string', 'max:50'],
|
||||
'nationality' => ['nullable', 'string', 'max:50'],
|
||||
'nik' => ['nullable', 'string', 'max:30', Rule::unique('students', 'nik')->ignore($student->id)],
|
||||
'family_card_number' => ['nullable', 'string', 'max:30'],
|
||||
'child_order' => ['nullable', 'integer', 'min:1', 'max:50'],
|
||||
'number_of_siblings' => ['nullable', 'integer', 'min:0', 'max:50'],
|
||||
@@ -171,6 +172,7 @@ class PublicStudentSelfServiceController extends Controller
|
||||
'date_of_birth' => $data['date_of_birth'] ?? null,
|
||||
'religion' => $data['religion'] ?? null,
|
||||
'nationality' => $data['nationality'] ?? 'Indonesia',
|
||||
'nik' => $data['nik'] ?? null,
|
||||
'family_card_number' => $data['family_card_number'] ?? null,
|
||||
'child_order' => $data['child_order'] ?? null,
|
||||
'number_of_siblings' => $data['number_of_siblings'] ?? null,
|
||||
|
||||
@@ -18,8 +18,10 @@ class StudentAchievementController extends Controller
|
||||
$search = trim((string) $request->get('search'));
|
||||
$academicYearId = $request->get('academic_year_id');
|
||||
$classId = $request->get('class_id');
|
||||
$level = trim((string) $request->get('level'));
|
||||
$category = trim((string) $request->get('category'));
|
||||
|
||||
$achievements = StudentAchievement::query()
|
||||
$achievementsQuery = StudentAchievement::query()
|
||||
->with(['student.academicYear', 'student.department', 'student.classRoom'])
|
||||
->when($search !== '', function ($query) use ($search) {
|
||||
$query->where(function ($subQuery) use ($search) {
|
||||
@@ -35,25 +37,67 @@ class StudentAchievementController extends Controller
|
||||
})
|
||||
->when($academicYearId, fn ($query) => $query->whereHas('student', fn ($studentQuery) => $studentQuery->where('academic_year_id', $academicYearId)))
|
||||
->when($classId, fn ($query) => $query->whereHas('student', fn ($studentQuery) => $studentQuery->where('class_id', $classId)))
|
||||
->when($level !== '', fn ($query) => $query->where('level', $level))
|
||||
->when($category !== '', fn ($query) => $query->where('category', $category));
|
||||
|
||||
$achievements = (clone $achievementsQuery)
|
||||
->latest('achievement_year')
|
||||
->latest('achievement_date')
|
||||
->latest('id')
|
||||
->paginate(15)
|
||||
->withQueryString();
|
||||
|
||||
$levelCounts = StudentAchievement::query()
|
||||
->selectRaw('COALESCE(level, ?) as level_label, COUNT(*) as total', ['Tanpa tingkat'])
|
||||
->groupBy('level')
|
||||
->orderByDesc('total')
|
||||
->pluck('total', 'level_label');
|
||||
|
||||
$withCertificateCount = StudentAchievement::query()->whereNotNull('certificate_file')->count();
|
||||
$withPhotoCount = StudentAchievement::query()->whereNotNull('activity_photo')->count();
|
||||
$thisYearCount = StudentAchievement::query()->where('achievement_year', now()->year)->count();
|
||||
|
||||
$recentAchievements = StudentAchievement::query()
|
||||
->with(['student:id,full_name'])
|
||||
->latest('achievement_date')
|
||||
->latest('achievement_year')
|
||||
->latest('id')
|
||||
->limit(3)
|
||||
->get();
|
||||
|
||||
$categoryOptions = StudentAchievement::query()
|
||||
->whereNotNull('category')
|
||||
->where('category', '!=', '')
|
||||
->distinct()
|
||||
->orderBy('category')
|
||||
->pluck('category', 'category');
|
||||
|
||||
return view('student-achievements.index', [
|
||||
'achievements' => $achievements,
|
||||
'search' => $search,
|
||||
'filters' => [
|
||||
'academic_year_id' => $academicYearId,
|
||||
'class_id' => $classId,
|
||||
'level' => $level,
|
||||
'category' => $category,
|
||||
],
|
||||
'academicYears' => AcademicYear::orderBy('name')->pluck('name', 'id'),
|
||||
'classes' => SchoolClass::orderBy('name')->pluck('name', 'id'),
|
||||
'categoryOptions' => $categoryOptions,
|
||||
'levelOptions' => $this->levelOptions(),
|
||||
'summary' => [
|
||||
'total' => StudentAchievement::count(),
|
||||
'filtered' => $achievements->total(),
|
||||
'students' => StudentAchievement::distinct('student_id')->count('student_id'),
|
||||
'latest_year' => StudentAchievement::max('achievement_year'),
|
||||
'top_level' => $levelCounts->keys()->first(),
|
||||
'top_level_total' => $levelCounts->first(),
|
||||
'with_certificate' => $withCertificateCount,
|
||||
'without_certificate' => max(StudentAchievement::count() - $withCertificateCount, 0),
|
||||
'with_photo' => $withPhotoCount,
|
||||
'this_year' => $thisYearCount,
|
||||
],
|
||||
'recentAchievements' => $recentAchievements,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ class StudentController extends Controller
|
||||
$subQuery->where('students.full_name', 'like', "%{$search}%")
|
||||
->orWhere('students.student_number', 'like', "%{$search}%")
|
||||
->orWhere('students.nisn', 'like', "%{$search}%")
|
||||
->orWhere('students.nik', 'like', "%{$search}%")
|
||||
->orWhere('students.phone_number', 'like', "%{$search}%");
|
||||
});
|
||||
})
|
||||
@@ -161,6 +162,7 @@ class StudentController extends Controller
|
||||
$subQuery->where('students.full_name', 'like', "%{$search}%")
|
||||
->orWhere('students.student_number', 'like', "%{$search}%")
|
||||
->orWhere('students.nisn', 'like', "%{$search}%")
|
||||
->orWhere('students.nik', 'like', "%{$search}%")
|
||||
->orWhere('students.phone_number', 'like', "%{$search}%");
|
||||
});
|
||||
})
|
||||
@@ -276,9 +278,14 @@ class StudentController extends Controller
|
||||
|
||||
public function edit(Student $student): View
|
||||
{
|
||||
return view('students.edit', array_merge($this->formData(), [
|
||||
'student' => $student,
|
||||
]));
|
||||
$student->loadMissing('classRoom');
|
||||
|
||||
return view('students.edit', array_merge(
|
||||
$this->formData($student),
|
||||
[
|
||||
'student' => $student,
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
public function update(Request $request, Student $student): RedirectResponse
|
||||
@@ -395,14 +402,34 @@ class StudentController extends Controller
|
||||
$payload = Arr::only($row['normalized'], [
|
||||
'student_number',
|
||||
'nisn',
|
||||
'nik',
|
||||
'full_name',
|
||||
'gender',
|
||||
'place_of_birth',
|
||||
'date_of_birth',
|
||||
'religion',
|
||||
'nationality',
|
||||
'family_card_number',
|
||||
'child_order',
|
||||
'number_of_siblings',
|
||||
'phone_number',
|
||||
'email',
|
||||
'address',
|
||||
'province_id',
|
||||
'regency_id',
|
||||
'district_id',
|
||||
'village_id',
|
||||
'province',
|
||||
'city',
|
||||
'district',
|
||||
'village',
|
||||
'postal_code',
|
||||
'academic_year_id',
|
||||
'department_id',
|
||||
'class_id',
|
||||
'entry_date',
|
||||
'status',
|
||||
'phone_number',
|
||||
'notes',
|
||||
]);
|
||||
|
||||
$lookup = null;
|
||||
@@ -422,12 +449,14 @@ class StudentController extends Controller
|
||||
|
||||
$lookup->update($payload);
|
||||
$this->syncAcademicHistory($lookup, $payload);
|
||||
$this->syncImportedParents($lookup, $row['normalized']['parents'] ?? []);
|
||||
$updated++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$student = Student::create($payload);
|
||||
$this->syncAcademicHistory($student, $payload);
|
||||
$this->syncImportedParents($student, $row['normalized']['parents'] ?? []);
|
||||
$inserted++;
|
||||
}
|
||||
});
|
||||
@@ -441,7 +470,7 @@ class StudentController extends Controller
|
||||
->with('success', "Import data siswa selesai. {$inserted} data baru ditambahkan, {$updated} data diperbarui.");
|
||||
}
|
||||
|
||||
public function downloadImportTemplate(): Response
|
||||
public function downloadImportTemplate()
|
||||
{
|
||||
$this->ensureAdmin();
|
||||
|
||||
@@ -452,32 +481,112 @@ class StudentController extends Controller
|
||||
$headers = [
|
||||
'NIS',
|
||||
'NISN',
|
||||
'NIK',
|
||||
'Nama Lengkap',
|
||||
'Jenis Kelamin (L/P)',
|
||||
'Tempat Lahir',
|
||||
'Tanggal Lahir (YYYY-MM-DD)',
|
||||
'Agama',
|
||||
'Kewarganegaraan',
|
||||
'No. Kartu Keluarga',
|
||||
'Anak Ke',
|
||||
'Jumlah Saudara',
|
||||
'Nomor HP',
|
||||
'Email',
|
||||
'Alamat',
|
||||
'Provinsi',
|
||||
'Kabupaten/Kota',
|
||||
'Kecamatan',
|
||||
'Kelurahan/Desa',
|
||||
'Kode Pos',
|
||||
'Tahun Ajaran',
|
||||
'Program Keahlian',
|
||||
'Kelas',
|
||||
'Tanggal Masuk (YYYY-MM-DD)',
|
||||
'Status',
|
||||
'Nomor HP',
|
||||
'Catatan',
|
||||
'Nama Ayah',
|
||||
'NIK Ayah',
|
||||
'Tahun Lahir Ayah',
|
||||
'Pendidikan Ayah',
|
||||
'Pekerjaan Ayah',
|
||||
'No. HP Ayah',
|
||||
'Alamat Ayah',
|
||||
'Status Hidup Ayah',
|
||||
'Nama Ibu',
|
||||
'NIK Ibu',
|
||||
'Tahun Lahir Ibu',
|
||||
'Pendidikan Ibu',
|
||||
'Pekerjaan Ibu',
|
||||
'No. HP Ibu',
|
||||
'Alamat Ibu',
|
||||
'Status Hidup Ibu',
|
||||
'Nama Wali',
|
||||
'NIK Wali',
|
||||
'Tahun Lahir Wali',
|
||||
'Pendidikan Wali',
|
||||
'Pekerjaan Wali',
|
||||
'No. HP Wali',
|
||||
'Alamat Wali',
|
||||
'Status Hidup Wali',
|
||||
];
|
||||
|
||||
$sheet->fromArray($headers, null, 'A1');
|
||||
$sheet->fromArray([
|
||||
'2026-001',
|
||||
'9988776655',
|
||||
'3512345678901234',
|
||||
'Contoh Nama Siswa',
|
||||
'L',
|
||||
'Malang',
|
||||
'2010-01-15',
|
||||
'Islam',
|
||||
'Indonesia',
|
||||
'3512345678901235',
|
||||
'1',
|
||||
'2',
|
||||
'081234567890',
|
||||
'contoh@siswa.local',
|
||||
'Jl. Contoh No. 1',
|
||||
'Jawa Timur',
|
||||
'Kota Malang',
|
||||
'Klojen',
|
||||
'Kauman',
|
||||
'65119',
|
||||
AcademicYear::orderBy('name')->value('name') ?? '2026/2027',
|
||||
Department::orderBy('name')->value('name') ?? 'DKV',
|
||||
SchoolClass::orderBy('name')->value('name') ?? 'X DKV 1',
|
||||
'2026-07-15',
|
||||
'active',
|
||||
'081234567890',
|
||||
'Catatan opsional',
|
||||
'Budi Santoso',
|
||||
'3512345678901111',
|
||||
'1980',
|
||||
'SMA',
|
||||
'Wiraswasta',
|
||||
'081200000001',
|
||||
'Jl. Ayah No. 1',
|
||||
'Ya',
|
||||
'Siti Aminah',
|
||||
'3512345678902222',
|
||||
'1982',
|
||||
'SMA',
|
||||
'Ibu Rumah Tangga',
|
||||
'081200000002',
|
||||
'Jl. Ibu No. 1',
|
||||
'Ya',
|
||||
'Ahmad Wali',
|
||||
'3512345678903333',
|
||||
'1975',
|
||||
'S1',
|
||||
'Pegawai Swasta',
|
||||
'081200000003',
|
||||
'Jl. Wali No. 1',
|
||||
'Ya',
|
||||
], null, 'A2');
|
||||
|
||||
foreach (range('A', 'J') as $column) {
|
||||
$sheet->getColumnDimension($column)->setAutoSize(true);
|
||||
for ($columnIndex = 1; $columnIndex <= count($headers); $columnIndex++) {
|
||||
$sheet->getColumnDimensionByColumn($columnIndex)->setAutoSize(true);
|
||||
}
|
||||
|
||||
$referenceSheet = $spreadsheet->createSheet();
|
||||
@@ -502,14 +611,54 @@ class StudentController extends Controller
|
||||
['Kolom', 'Keterangan'],
|
||||
['NIS', 'Boleh kosong, tapi idealnya diisi untuk identitas lokal sekolah.'],
|
||||
['NISN', 'Boleh kosong jika belum ada.'],
|
||||
['NIK', 'Opsional, tetapi jika diisi harus unik.'],
|
||||
['Nama Lengkap', 'Wajib diisi.'],
|
||||
['Jenis Kelamin (L/P)', 'Isi L atau P.'],
|
||||
['Tempat Lahir', 'Opsional.'],
|
||||
['Tanggal Lahir (YYYY-MM-DD)', 'Format contoh: 2010-01-15.'],
|
||||
['Agama', 'Opsional, contoh: Islam, Kristen, Katolik, Hindu, Buddha, Konghucu.'],
|
||||
['Kewarganegaraan', 'Opsional, default aman: Indonesia.'],
|
||||
['No. Kartu Keluarga', 'Opsional.'],
|
||||
['Anak Ke', 'Opsional, angka bulat minimal 1.'],
|
||||
['Jumlah Saudara', 'Opsional, angka bulat minimal 0.'],
|
||||
['Nomor HP', 'Opsional.'],
|
||||
['Email', 'Opsional, harus format email valid bila diisi.'],
|
||||
['Alamat', 'Opsional.'],
|
||||
['Provinsi', 'Opsional, isi nama persis seperti data master wilayah.'],
|
||||
['Kabupaten/Kota', 'Opsional, isi nama persis seperti data master wilayah.'],
|
||||
['Kecamatan', 'Opsional, isi nama persis seperti data master wilayah.'],
|
||||
['Kelurahan/Desa', 'Opsional, isi nama persis seperti data master wilayah.'],
|
||||
['Kode Pos', 'Opsional.'],
|
||||
['Tahun Ajaran', 'Isi dengan nama tahun ajaran persis seperti sheet Referensi.'],
|
||||
['Program Keahlian', 'Isi dengan nama jurusan persis seperti sheet Referensi.'],
|
||||
['Kelas', 'Isi dengan nama kelas persis seperti sheet Referensi.'],
|
||||
['Tanggal Masuk (YYYY-MM-DD)', 'Format contoh: 2026-07-15.'],
|
||||
['Status', 'Gunakan: active, inactive, transferred, graduated, dropped.'],
|
||||
['Nomor HP', 'Opsional.'],
|
||||
['Catatan', 'Opsional.'],
|
||||
['Nama Ayah', 'Opsional.'],
|
||||
['NIK Ayah', 'Opsional.'],
|
||||
['Tahun Lahir Ayah', 'Opsional, angka 4 digit.'],
|
||||
['Pendidikan Ayah', 'Opsional.'],
|
||||
['Pekerjaan Ayah', 'Opsional.'],
|
||||
['No. HP Ayah', 'Opsional.'],
|
||||
['Alamat Ayah', 'Opsional.'],
|
||||
['Status Hidup Ayah', 'Opsional, isi Ya/Tidak atau Hidup/Meninggal.'],
|
||||
['Nama Ibu', 'Opsional.'],
|
||||
['NIK Ibu', 'Opsional.'],
|
||||
['Tahun Lahir Ibu', 'Opsional, angka 4 digit.'],
|
||||
['Pendidikan Ibu', 'Opsional.'],
|
||||
['Pekerjaan Ibu', 'Opsional.'],
|
||||
['No. HP Ibu', 'Opsional.'],
|
||||
['Alamat Ibu', 'Opsional.'],
|
||||
['Status Hidup Ibu', 'Opsional, isi Ya/Tidak atau Hidup/Meninggal.'],
|
||||
['Nama Wali', 'Opsional.'],
|
||||
['NIK Wali', 'Opsional.'],
|
||||
['Tahun Lahir Wali', 'Opsional, angka 4 digit.'],
|
||||
['Pendidikan Wali', 'Opsional.'],
|
||||
['Pekerjaan Wali', 'Opsional.'],
|
||||
['No. HP Wali', 'Opsional.'],
|
||||
['Alamat Wali', 'Opsional.'],
|
||||
['Status Hidup Wali', 'Opsional, isi Ya/Tidak atau Hidup/Meninggal.'],
|
||||
], null, 'A1');
|
||||
foreach (range('A', 'B') as $column) {
|
||||
$guideSheet->getColumnDimension($column)->setAutoSize(true);
|
||||
@@ -526,11 +675,26 @@ class StudentController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function downloadImportExample()
|
||||
{
|
||||
$this->ensureAdmin();
|
||||
|
||||
$path = storage_path('app/private/student-import-example.xlsx');
|
||||
|
||||
abort_unless(file_exists($path), 404, 'File contoh import siswa belum tersedia.');
|
||||
|
||||
return response()->file($path, [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'Content-Disposition' => 'attachment; filename="contoh-import-siswa.xlsx"',
|
||||
]);
|
||||
}
|
||||
|
||||
protected function validateStudent(Request $request, ?Student $student = null): array
|
||||
{
|
||||
$data = $request->validate([
|
||||
'student_number' => ['nullable', 'string', 'max:30', Rule::unique('students', 'student_number')->ignore($student?->id)],
|
||||
'nisn' => ['nullable', 'string', 'max:30', Rule::unique('students', 'nisn')->ignore($student?->id)],
|
||||
'nik' => ['nullable', 'string', 'max:30', Rule::unique('students', 'nik')->ignore($student?->id)],
|
||||
'full_name' => ['required', 'string', 'max:150'],
|
||||
'gender' => ['required', Rule::in(['L', 'P'])],
|
||||
'place_of_birth' => ['nullable', 'string', 'max:100'],
|
||||
@@ -598,16 +762,22 @@ class StudentController extends Controller
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function formData(): array
|
||||
protected function formData(?Student $student = null): array
|
||||
{
|
||||
$classOptionsQuery = SchoolClass::query()
|
||||
->when(
|
||||
$student?->class_id,
|
||||
fn ($query) => $query->where('is_active', true)->orWhere('id', $student->class_id),
|
||||
fn ($query) => $query->where('is_active', true)
|
||||
)
|
||||
->orderBy('name');
|
||||
|
||||
return [
|
||||
'academicYears' => AcademicYear::where('is_active', true)->orderBy('name')->pluck('name', 'id'),
|
||||
'departments' => Department::where('is_active', true)->orderBy('name')->pluck('name', 'id'),
|
||||
'classes' => SchoolClass::where('is_active', true)->orderBy('name')->pluck('name', 'id'),
|
||||
'provinces' => Province::orderBy('name')->pluck('name', 'id'),
|
||||
'classOptions' => SchoolClass::query()
|
||||
->where('is_active', true)
|
||||
->orderBy('name')
|
||||
'classOptions' => $classOptionsQuery
|
||||
->get(['id', 'name', 'department_id', 'academic_year_id'])
|
||||
->map(fn ($classRoom) => [
|
||||
'id' => $classRoom->id,
|
||||
@@ -825,6 +995,48 @@ class StudentController extends Controller
|
||||
$graduationRecord->ijazah_file_size = $file->getSize();
|
||||
}
|
||||
|
||||
if (request()->hasFile('transcript_file')) {
|
||||
if ($graduationRecord->transcript_file_path && Storage::disk('public')->exists($graduationRecord->transcript_file_path)) {
|
||||
Storage::disk('public')->delete($graduationRecord->transcript_file_path);
|
||||
}
|
||||
|
||||
$file = request()->file('transcript_file');
|
||||
$path = $file->store("student-graduation-documents/{$student->id}/transcript", 'public');
|
||||
|
||||
$graduationRecord->transcript_file_name = $file->getClientOriginalName();
|
||||
$graduationRecord->transcript_file_path = $path;
|
||||
$graduationRecord->transcript_mime_type = $file->getMimeType();
|
||||
$graduationRecord->transcript_file_size = $file->getSize();
|
||||
}
|
||||
|
||||
if (request()->hasFile('ukk_certificate_file')) {
|
||||
if ($graduationRecord->ukk_certificate_file_path && Storage::disk('public')->exists($graduationRecord->ukk_certificate_file_path)) {
|
||||
Storage::disk('public')->delete($graduationRecord->ukk_certificate_file_path);
|
||||
}
|
||||
|
||||
$file = request()->file('ukk_certificate_file');
|
||||
$path = $file->store("student-graduation-documents/{$student->id}/ukk-certificate", 'public');
|
||||
|
||||
$graduationRecord->ukk_certificate_file_name = $file->getClientOriginalName();
|
||||
$graduationRecord->ukk_certificate_file_path = $path;
|
||||
$graduationRecord->ukk_certificate_mime_type = $file->getMimeType();
|
||||
$graduationRecord->ukk_certificate_file_size = $file->getSize();
|
||||
}
|
||||
|
||||
if (request()->hasFile('lsp_certificate_file')) {
|
||||
if ($graduationRecord->lsp_certificate_file_path && Storage::disk('public')->exists($graduationRecord->lsp_certificate_file_path)) {
|
||||
Storage::disk('public')->delete($graduationRecord->lsp_certificate_file_path);
|
||||
}
|
||||
|
||||
$file = request()->file('lsp_certificate_file');
|
||||
$path = $file->store("student-graduation-documents/{$student->id}/lsp-certificate", 'public');
|
||||
|
||||
$graduationRecord->lsp_certificate_file_name = $file->getClientOriginalName();
|
||||
$graduationRecord->lsp_certificate_file_path = $path;
|
||||
$graduationRecord->lsp_certificate_mime_type = $file->getMimeType();
|
||||
$graduationRecord->lsp_certificate_file_size = $file->getSize();
|
||||
}
|
||||
|
||||
$graduationRecord->save();
|
||||
|
||||
return;
|
||||
@@ -838,14 +1050,54 @@ class StudentController extends Controller
|
||||
return [
|
||||
'student_number' => ['nullable', 'string', 'max:30'],
|
||||
'nisn' => ['nullable', 'string', 'max:30'],
|
||||
'nik' => ['nullable', 'string', 'max:30'],
|
||||
'full_name' => ['required', 'string', 'max:150'],
|
||||
'gender' => ['required', Rule::in(['L', 'P'])],
|
||||
'place_of_birth' => ['nullable', 'string', 'max:100'],
|
||||
'date_of_birth' => ['nullable', 'date'],
|
||||
'religion' => ['nullable', 'string', 'max:50'],
|
||||
'nationality' => ['nullable', 'string', 'max:50'],
|
||||
'family_card_number' => ['nullable', 'string', 'max:30'],
|
||||
'child_order' => ['nullable', 'integer', 'min:1'],
|
||||
'number_of_siblings' => ['nullable', 'integer', 'min:0'],
|
||||
'phone_number' => ['nullable', 'string', 'max:30'],
|
||||
'email' => ['nullable', 'email', 'max:100'],
|
||||
'address' => ['nullable', 'string'],
|
||||
'province_id' => ['nullable', 'string', 'exists:provinces,id'],
|
||||
'regency_id' => ['nullable', 'string', 'exists:regencies,id'],
|
||||
'district_id' => ['nullable', 'string', 'exists:districts,id'],
|
||||
'village_id' => ['nullable', 'string', 'exists:villages,id'],
|
||||
'postal_code' => ['nullable', 'string', 'max:10'],
|
||||
'academic_year_id' => ['required', 'integer', Rule::exists('academic_years', 'id')->where(fn ($query) => $query->where('is_active', true))],
|
||||
'department_id' => ['required', 'integer', Rule::exists('departments', 'id')->where(fn ($query) => $query->where('is_active', true))],
|
||||
'class_id' => ['required', 'integer', Rule::exists('classes', 'id')->where(fn ($query) => $query->where('is_active', true))],
|
||||
'entry_date' => ['nullable', 'date'],
|
||||
'status' => ['required', Rule::in(array_keys($this->statusOptions()))],
|
||||
'phone_number' => ['nullable', 'string', 'max:30'],
|
||||
'notes' => ['nullable', 'string'],
|
||||
'parents.father.name' => ['nullable', 'string', 'max:150'],
|
||||
'parents.father.nik' => ['nullable', 'string', 'max:30'],
|
||||
'parents.father.birth_year' => ['nullable', 'integer', 'min:1900', 'max:' . now()->year],
|
||||
'parents.father.education' => ['nullable', 'string', 'max:100'],
|
||||
'parents.father.occupation' => ['nullable', 'string', 'max:100'],
|
||||
'parents.father.phone_number' => ['nullable', 'string', 'max:30'],
|
||||
'parents.father.address' => ['nullable', 'string'],
|
||||
'parents.father.is_alive' => ['nullable', 'boolean'],
|
||||
'parents.mother.name' => ['nullable', 'string', 'max:150'],
|
||||
'parents.mother.nik' => ['nullable', 'string', 'max:30'],
|
||||
'parents.mother.birth_year' => ['nullable', 'integer', 'min:1900', 'max:' . now()->year],
|
||||
'parents.mother.education' => ['nullable', 'string', 'max:100'],
|
||||
'parents.mother.occupation' => ['nullable', 'string', 'max:100'],
|
||||
'parents.mother.phone_number' => ['nullable', 'string', 'max:30'],
|
||||
'parents.mother.address' => ['nullable', 'string'],
|
||||
'parents.mother.is_alive' => ['nullable', 'boolean'],
|
||||
'parents.guardian.name' => ['nullable', 'string', 'max:150'],
|
||||
'parents.guardian.nik' => ['nullable', 'string', 'max:30'],
|
||||
'parents.guardian.birth_year' => ['nullable', 'integer', 'min:1900', 'max:' . now()->year],
|
||||
'parents.guardian.education' => ['nullable', 'string', 'max:100'],
|
||||
'parents.guardian.occupation' => ['nullable', 'string', 'max:100'],
|
||||
'parents.guardian.phone_number' => ['nullable', 'string', 'max:30'],
|
||||
'parents.guardian.address' => ['nullable', 'string'],
|
||||
'parents.guardian.is_alive' => ['nullable', 'boolean'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -854,14 +1106,54 @@ class StudentController extends Controller
|
||||
return [
|
||||
'student_number' => 'NIS',
|
||||
'nisn' => 'NISN',
|
||||
'nik' => 'NIK',
|
||||
'full_name' => 'nama lengkap',
|
||||
'gender' => 'jenis kelamin',
|
||||
'place_of_birth' => 'tempat lahir',
|
||||
'date_of_birth' => 'tanggal lahir',
|
||||
'religion' => 'agama',
|
||||
'nationality' => 'kewarganegaraan',
|
||||
'family_card_number' => 'nomor kartu keluarga',
|
||||
'child_order' => 'anak ke',
|
||||
'number_of_siblings' => 'jumlah saudara',
|
||||
'phone_number' => 'nomor HP',
|
||||
'email' => 'email',
|
||||
'address' => 'alamat',
|
||||
'province_id' => 'provinsi',
|
||||
'regency_id' => 'kabupaten/kota',
|
||||
'district_id' => 'kecamatan',
|
||||
'village_id' => 'kelurahan/desa',
|
||||
'postal_code' => 'kode pos',
|
||||
'academic_year_id' => 'tahun ajaran',
|
||||
'department_id' => 'program keahlian',
|
||||
'class_id' => 'kelas',
|
||||
'entry_date' => 'tanggal masuk',
|
||||
'status' => 'status',
|
||||
'phone_number' => 'nomor HP',
|
||||
'notes' => 'catatan',
|
||||
'parents.father.name' => 'nama ayah',
|
||||
'parents.father.nik' => 'NIK ayah',
|
||||
'parents.father.birth_year' => 'tahun lahir ayah',
|
||||
'parents.father.education' => 'pendidikan ayah',
|
||||
'parents.father.occupation' => 'pekerjaan ayah',
|
||||
'parents.father.phone_number' => 'nomor HP ayah',
|
||||
'parents.father.address' => 'alamat ayah',
|
||||
'parents.father.is_alive' => 'status hidup ayah',
|
||||
'parents.mother.name' => 'nama ibu',
|
||||
'parents.mother.nik' => 'NIK ibu',
|
||||
'parents.mother.birth_year' => 'tahun lahir ibu',
|
||||
'parents.mother.education' => 'pendidikan ibu',
|
||||
'parents.mother.occupation' => 'pekerjaan ibu',
|
||||
'parents.mother.phone_number' => 'nomor HP ibu',
|
||||
'parents.mother.address' => 'alamat ibu',
|
||||
'parents.mother.is_alive' => 'status hidup ibu',
|
||||
'parents.guardian.name' => 'nama wali',
|
||||
'parents.guardian.nik' => 'NIK wali',
|
||||
'parents.guardian.birth_year' => 'tahun lahir wali',
|
||||
'parents.guardian.education' => 'pendidikan wali',
|
||||
'parents.guardian.occupation' => 'pekerjaan wali',
|
||||
'parents.guardian.phone_number' => 'nomor HP wali',
|
||||
'parents.guardian.address' => 'alamat wali',
|
||||
'parents.guardian.is_alive' => 'status hidup wali',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -968,30 +1260,75 @@ class StudentController extends Controller
|
||||
|
||||
$studentNumber = $this->nullIfBlank($normalized['student_number'] ?? ($normalized['nis'] ?? null));
|
||||
$nisn = $this->nullIfBlank($normalized['nisn'] ?? null);
|
||||
$nik = $this->nullIfBlank($normalized['nik'] ?? null);
|
||||
$fullName = $this->nullIfBlank($normalized['full_name'] ?? ($normalized['nama_lengkap'] ?? null));
|
||||
$gender = strtoupper((string) $this->nullIfBlank($normalized['gender'] ?? ($normalized['jenis_kelamin_l_p'] ?? null)));
|
||||
$status = strtolower((string) ($this->nullIfBlank($normalized['status'] ?? null) ?? 'active'));
|
||||
$entryDate = $this->nullIfBlank($normalized['entry_date'] ?? ($normalized['tanggal_masuk_yyyy_mm_dd'] ?? null));
|
||||
$placeOfBirth = $this->nullIfBlank($normalized['place_of_birth'] ?? ($normalized['tempat_lahir'] ?? null));
|
||||
$dateOfBirth = $this->normalizeImportDate($normalized['date_of_birth'] ?? ($normalized['tanggal_lahir_yyyy_mm_dd'] ?? null));
|
||||
$religion = $this->nullIfBlank($normalized['religion'] ?? ($normalized['agama'] ?? null));
|
||||
$nationality = $this->nullIfBlank($normalized['nationality'] ?? ($normalized['kewarganegaraan'] ?? null));
|
||||
$familyCardNumber = $this->nullIfBlank($normalized['family_card_number'] ?? ($normalized['no_kartu_keluarga'] ?? null));
|
||||
$childOrder = $this->normalizeImportInteger($normalized['child_order'] ?? ($normalized['anak_ke'] ?? null));
|
||||
$numberOfSiblings = $this->normalizeImportInteger($normalized['number_of_siblings'] ?? ($normalized['jumlah_saudara'] ?? null));
|
||||
$phoneNumber = $this->nullIfBlank($normalized['phone_number'] ?? ($normalized['nomor_hp'] ?? null));
|
||||
$email = $this->nullIfBlank($normalized['email'] ?? null);
|
||||
$address = $this->nullIfBlank($normalized['address'] ?? ($normalized['alamat'] ?? null));
|
||||
$postalCode = $this->nullIfBlank($normalized['postal_code'] ?? ($normalized['kode_pos'] ?? null));
|
||||
$status = strtolower((string) ($this->nullIfBlank($normalized['status'] ?? null) ?? 'active'));
|
||||
$entryDate = $this->normalizeImportDate($normalized['entry_date'] ?? ($normalized['tanggal_masuk_yyyy_mm_dd'] ?? null));
|
||||
$notes = $this->nullIfBlank($normalized['notes'] ?? ($normalized['catatan'] ?? null));
|
||||
$academicYearSource = $this->nullIfBlank($normalized['academic_year_id'] ?? ($normalized['tahun_ajaran'] ?? null));
|
||||
$departmentSource = $this->nullIfBlank($normalized['department_id'] ?? ($normalized['program_keahlian'] ?? null));
|
||||
$classSource = $this->nullIfBlank($normalized['class_id'] ?? ($normalized['kelas'] ?? null));
|
||||
$provinceSource = $this->nullIfBlank($normalized['province_id'] ?? ($normalized['provinsi'] ?? null));
|
||||
$regencySource = $this->nullIfBlank($normalized['regency_id'] ?? ($normalized['kabupaten_kota'] ?? null));
|
||||
$districtSource = $this->nullIfBlank($normalized['district_id'] ?? ($normalized['kecamatan'] ?? null));
|
||||
$villageSource = $this->nullIfBlank($normalized['village_id'] ?? ($normalized['kelurahan_desa'] ?? null));
|
||||
|
||||
$academicYearId = $this->resolveAcademicYearId($academicYearSource);
|
||||
$departmentId = $this->resolveDepartmentId($departmentSource);
|
||||
$classId = $this->resolveClassId($classSource, $academicYearId, $departmentId);
|
||||
$provinceId = $this->resolveProvinceId($provinceSource);
|
||||
$regencyId = $this->resolveRegencyId($regencySource, $provinceId);
|
||||
$districtId = $this->resolveDistrictId($districtSource, $regencyId);
|
||||
$villageId = $this->resolveVillageId($villageSource, $districtId);
|
||||
|
||||
return [
|
||||
'student_number' => $studentNumber,
|
||||
'nisn' => $nisn,
|
||||
'nik' => $nik,
|
||||
'full_name' => $fullName,
|
||||
'gender' => $gender,
|
||||
'place_of_birth' => $placeOfBirth,
|
||||
'date_of_birth' => $dateOfBirth,
|
||||
'religion' => $religion,
|
||||
'nationality' => $nationality,
|
||||
'family_card_number' => $familyCardNumber,
|
||||
'child_order' => $childOrder,
|
||||
'number_of_siblings' => $numberOfSiblings,
|
||||
'phone_number' => $phoneNumber,
|
||||
'email' => $email,
|
||||
'address' => $address,
|
||||
'province_id' => $provinceId,
|
||||
'regency_id' => $regencyId,
|
||||
'district_id' => $districtId,
|
||||
'village_id' => $villageId,
|
||||
'province' => $provinceId ? Province::where('id', $provinceId)->value('name') : null,
|
||||
'city' => $regencyId ? Regency::where('id', $regencyId)->value('name') : null,
|
||||
'district' => $districtId ? District::where('id', $districtId)->value('name') : null,
|
||||
'village' => $villageId ? Village::where('id', $villageId)->value('name') : null,
|
||||
'postal_code' => $postalCode,
|
||||
'academic_year_id' => $academicYearId,
|
||||
'department_id' => $departmentId,
|
||||
'class_id' => $classId,
|
||||
'entry_date' => $entryDate,
|
||||
'status' => $status,
|
||||
'phone_number' => $phoneNumber,
|
||||
'notes' => $notes,
|
||||
'parents' => [
|
||||
'father' => $this->normalizeImportParentData($normalized, 'father', 'ayah'),
|
||||
'mother' => $this->normalizeImportParentData($normalized, 'mother', 'ibu'),
|
||||
'guardian' => $this->normalizeImportParentData($normalized, 'guardian', 'wali'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1038,6 +1375,181 @@ class StudentController extends Controller
|
||||
->value('id');
|
||||
}
|
||||
|
||||
protected function resolveProvinceId(mixed $value): mixed
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
return Province::where('name', trim((string) $value))->value('id');
|
||||
}
|
||||
|
||||
protected function resolveRegencyId(mixed $value, mixed $provinceId = null): mixed
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
return Regency::query()
|
||||
->when($provinceId, fn ($query) => $query->where('province_id', $provinceId))
|
||||
->where('name', trim((string) $value))
|
||||
->value('id');
|
||||
}
|
||||
|
||||
protected function resolveDistrictId(mixed $value, mixed $regencyId = null): mixed
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
return District::query()
|
||||
->when($regencyId, fn ($query) => $query->where('regency_id', $regencyId))
|
||||
->where('name', trim((string) $value))
|
||||
->value('id');
|
||||
}
|
||||
|
||||
protected function resolveVillageId(mixed $value, mixed $districtId = null): mixed
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
return Village::query()
|
||||
->when($districtId, fn ($query) => $query->where('district_id', $districtId))
|
||||
->where('name', trim((string) $value))
|
||||
->value('id');
|
||||
}
|
||||
|
||||
protected function normalizeImportDate(mixed $value): mixed
|
||||
{
|
||||
$value = $this->nullIfBlank($value);
|
||||
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
try {
|
||||
return \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject((float) $value)->format('Y-m-d');
|
||||
} catch (\Throwable) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected function normalizeImportInteger(mixed $value): mixed
|
||||
{
|
||||
$value = $this->nullIfBlank($value);
|
||||
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
return (int) $value;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected function normalizeImportBoolean(mixed $value): ?bool
|
||||
{
|
||||
$value = $this->nullIfBlank($value);
|
||||
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_bool($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$normalized = strtolower(trim((string) $value));
|
||||
|
||||
return match ($normalized) {
|
||||
'1', 'true', 'ya', 'y', 'yes', 'hidup', 'masih hidup' => true,
|
||||
'0', 'false', 'tidak', 't', 'no', 'meninggal', 'wafat' => false,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
protected function normalizeImportParentData(array $normalized, string $relationKey, string $legacyPrefix): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->nullIfBlank($normalized["parent_{$relationKey}_name"] ?? ($normalized["nama_{$legacyPrefix}"] ?? null)),
|
||||
'nik' => $this->nullIfBlank($normalized["parent_{$relationKey}_nik"] ?? ($normalized["nik_{$legacyPrefix}"] ?? null)),
|
||||
'birth_year' => $this->normalizeImportInteger($normalized["parent_{$relationKey}_birth_year"] ?? ($normalized["tahun_lahir_{$legacyPrefix}"] ?? null)),
|
||||
'education' => $this->nullIfBlank($normalized["parent_{$relationKey}_education"] ?? ($normalized["pendidikan_{$legacyPrefix}"] ?? null)),
|
||||
'occupation' => $this->nullIfBlank($normalized["parent_{$relationKey}_occupation"] ?? ($normalized["pekerjaan_{$legacyPrefix}"] ?? null)),
|
||||
'phone_number' => $this->nullIfBlank($normalized["parent_{$relationKey}_phone_number"] ?? ($normalized["no_hp_{$legacyPrefix}"] ?? null)),
|
||||
'address' => $this->nullIfBlank($normalized["parent_{$relationKey}_address"] ?? ($normalized["alamat_{$legacyPrefix}"] ?? null)),
|
||||
'is_alive' => $this->normalizeImportBoolean($normalized["parent_{$relationKey}_is_alive"] ?? ($normalized["status_hidup_{$legacyPrefix}"] ?? null)),
|
||||
];
|
||||
}
|
||||
|
||||
protected function syncImportedParents(Student $student, array $parents): void
|
||||
{
|
||||
foreach (['father', 'mother', 'guardian'] as $relationType) {
|
||||
$parentData = $parents[$relationType] ?? [];
|
||||
$hasMeaningfulValue = collect($parentData)
|
||||
->except('is_alive')
|
||||
->filter(fn ($value) => filled($value))
|
||||
->isNotEmpty();
|
||||
|
||||
if (! $hasMeaningfulValue && ! array_key_exists('is_alive', $parentData)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$existingParent = $student->parents()->where('relation_type', $relationType)->first();
|
||||
|
||||
if (! $hasMeaningfulValue) {
|
||||
if ($existingParent) {
|
||||
$existingParent->delete();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'name' => $parentData['name'] ?? '-',
|
||||
'nik' => $parentData['nik'] ?? null,
|
||||
'birth_year' => $parentData['birth_year'] ?? null,
|
||||
'education' => $parentData['education'] ?? null,
|
||||
'occupation' => $parentData['occupation'] ?? null,
|
||||
'phone_number' => $parentData['phone_number'] ?? null,
|
||||
'address' => $parentData['address'] ?? null,
|
||||
'is_alive' => $parentData['is_alive'] ?? false,
|
||||
];
|
||||
|
||||
if ($existingParent) {
|
||||
$existingParent->update($payload);
|
||||
} else {
|
||||
$student->parents()->create([
|
||||
'relation_type' => $relationType,
|
||||
...$payload,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function parseImportFile(string $path, string $originalName): array
|
||||
{
|
||||
$extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
|
||||
|
||||
@@ -21,7 +21,7 @@ class StudentExitController extends Controller
|
||||
$departmentId = $request->get('department_id');
|
||||
$classId = $request->get('class_id');
|
||||
|
||||
$studentExits = StudentExit::query()
|
||||
$studentExitsQuery = StudentExit::query()
|
||||
->with(['academicYear', 'department', 'classRoom'])
|
||||
->when($search !== '', function ($query) use ($search) {
|
||||
$query->where(function ($subQuery) use ($search) {
|
||||
@@ -32,12 +32,25 @@ class StudentExitController extends Controller
|
||||
})
|
||||
->when($academicYearId, fn ($query) => $query->where('academic_year_id', $academicYearId))
|
||||
->when($departmentId, fn ($query) => $query->where('department_id', $departmentId))
|
||||
->when($classId, fn ($query) => $query->where('class_id', $classId))
|
||||
->when($classId, fn ($query) => $query->where('class_id', $classId));
|
||||
|
||||
$studentExits = (clone $studentExitsQuery)
|
||||
->latest('exit_date')
|
||||
->latest('id')
|
||||
->paginate(10)
|
||||
->withQueryString();
|
||||
|
||||
$totalExits = StudentExit::count();
|
||||
$withLetterCount = StudentExit::query()->whereNotNull('exit_letter_file_path')->count();
|
||||
$withoutLetterCount = max($totalExits - $withLetterCount, 0);
|
||||
$thisYearCount = StudentExit::query()->whereYear('exit_date', now()->year)->count();
|
||||
$latestExitDate = StudentExit::query()->max('exit_date');
|
||||
$recentExits = StudentExit::query()
|
||||
->latest('exit_date')
|
||||
->latest('id')
|
||||
->limit(3)
|
||||
->get(['id', 'full_name', 'exit_date', 'department_id', 'class_id']);
|
||||
|
||||
return view('student-exits.index', [
|
||||
'studentExits' => $studentExits,
|
||||
'search' => $search,
|
||||
@@ -49,6 +62,15 @@ class StudentExitController extends Controller
|
||||
'academicYears' => AcademicYear::orderBy('name')->pluck('name', 'id'),
|
||||
'departments' => Department::orderBy('name')->pluck('name', 'id'),
|
||||
'classes' => SchoolClass::orderBy('name')->pluck('name', 'id'),
|
||||
'summary' => [
|
||||
'total' => $totalExits,
|
||||
'with_letter' => $withLetterCount,
|
||||
'without_letter' => $withoutLetterCount,
|
||||
'this_year' => $thisYearCount,
|
||||
'latest_exit_date' => $latestExitDate,
|
||||
'filtered' => $studentExits->total(),
|
||||
],
|
||||
'recentExits' => $recentExits,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -158,8 +158,12 @@ class StudentSelfServiceController extends Controller
|
||||
|
||||
protected function generateUniqueCode(): string
|
||||
{
|
||||
$characters = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
|
||||
|
||||
do {
|
||||
$code = strtoupper(Str::random(8));
|
||||
$code = collect(range(1, 4))
|
||||
->map(fn () => $characters[random_int(0, strlen($characters) - 1)])
|
||||
->implode('');
|
||||
} while (Student::where('self_service_access_code', $code)->exists());
|
||||
|
||||
return $code;
|
||||
|
||||
@@ -21,7 +21,7 @@ class StudentTransferController extends Controller
|
||||
$departmentId = $request->get('department_id');
|
||||
$classId = $request->get('class_id');
|
||||
|
||||
$studentTransfers = StudentTransfer::query()
|
||||
$studentTransfersQuery = StudentTransfer::query()
|
||||
->with(['academicYear', 'department', 'classRoom'])
|
||||
->when($search !== '', function ($query) use ($search) {
|
||||
$query->where(function ($subQuery) use ($search) {
|
||||
@@ -32,12 +32,25 @@ class StudentTransferController extends Controller
|
||||
})
|
||||
->when($academicYearId, fn ($query) => $query->where('academic_year_id', $academicYearId))
|
||||
->when($departmentId, fn ($query) => $query->where('department_id', $departmentId))
|
||||
->when($classId, fn ($query) => $query->where('class_id', $classId))
|
||||
->when($classId, fn ($query) => $query->where('class_id', $classId));
|
||||
|
||||
$studentTransfers = (clone $studentTransfersQuery)
|
||||
->latest('transfer_date')
|
||||
->latest('id')
|
||||
->paginate(10)
|
||||
->withQueryString();
|
||||
|
||||
$totalTransfers = StudentTransfer::count();
|
||||
$withLetterCount = StudentTransfer::query()->whereNotNull('transfer_letter_file_path')->count();
|
||||
$withoutLetterCount = max($totalTransfers - $withLetterCount, 0);
|
||||
$thisYearCount = StudentTransfer::query()->whereYear('transfer_date', now()->year)->count();
|
||||
$latestTransferDate = StudentTransfer::query()->max('transfer_date');
|
||||
$recentTransfers = StudentTransfer::query()
|
||||
->latest('transfer_date')
|
||||
->latest('id')
|
||||
->limit(3)
|
||||
->get(['id', 'full_name', 'transfer_date', 'department_id', 'class_id']);
|
||||
|
||||
return view('student-transfers.index', [
|
||||
'studentTransfers' => $studentTransfers,
|
||||
'search' => $search,
|
||||
@@ -49,6 +62,15 @@ class StudentTransferController extends Controller
|
||||
'academicYears' => AcademicYear::orderBy('name')->pluck('name', 'id'),
|
||||
'departments' => Department::orderBy('name')->pluck('name', 'id'),
|
||||
'classes' => SchoolClass::orderBy('name')->pluck('name', 'id'),
|
||||
'summary' => [
|
||||
'total' => $totalTransfers,
|
||||
'with_letter' => $withLetterCount,
|
||||
'without_letter' => $withoutLetterCount,
|
||||
'this_year' => $thisYearCount,
|
||||
'latest_transfer_date' => $latestTransferDate,
|
||||
'filtered' => $studentTransfers->total(),
|
||||
],
|
||||
'recentTransfers' => $recentTransfers,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,14 +44,10 @@ class SyncTokenController extends Controller
|
||||
|
||||
public function revoke(SyncToken $syncToken): RedirectResponse
|
||||
{
|
||||
if (! $syncToken->revoked_at) {
|
||||
$syncToken->update([
|
||||
'revoked_at' => now(),
|
||||
]);
|
||||
}
|
||||
$syncToken->delete();
|
||||
|
||||
return redirect()
|
||||
->route('settings.sync-tokens.index')
|
||||
->with('status', 'Token sinkronisasi berhasil dinonaktifkan.');
|
||||
->with('status', 'Token sinkronisasi berhasil dihapus.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ class Student extends Model
|
||||
protected $fillable = [
|
||||
'student_number',
|
||||
'nisn',
|
||||
'nik',
|
||||
'full_name',
|
||||
'gender',
|
||||
'place_of_birth',
|
||||
|
||||
@@ -29,6 +29,18 @@ class StudentGraduation extends Model
|
||||
'ijazah_file_path',
|
||||
'ijazah_mime_type',
|
||||
'ijazah_file_size',
|
||||
'transcript_file_name',
|
||||
'transcript_file_path',
|
||||
'transcript_mime_type',
|
||||
'transcript_file_size',
|
||||
'ukk_certificate_file_name',
|
||||
'ukk_certificate_file_path',
|
||||
'ukk_certificate_mime_type',
|
||||
'ukk_certificate_file_size',
|
||||
'lsp_certificate_file_name',
|
||||
'lsp_certificate_file_path',
|
||||
'lsp_certificate_mime_type',
|
||||
'lsp_certificate_file_size',
|
||||
'status',
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user