feat: update student import and graduation workflows
This commit is contained in:
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user