527 lines
16 KiB
PHP
527 lines
16 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use App\Models\PengajuanUkt;
|
|
use App\Models\PengajuanDetail;
|
|
use App\Models\Kriteria;
|
|
use App\Models\Form;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Facades\DB;
|
|
use App\Notifications\PengajuanValidNotification;
|
|
use App\Notifications\PengajuanDitolakNotification;
|
|
use Illuminate\Support\Facades\Log;
|
|
use App\Models\RankingFile;
|
|
|
|
class AdminPengajuanController extends Controller
|
|
{
|
|
// Status mapping for consistent status values
|
|
const STATUS_MAPPING = [
|
|
'diterima' => 'valid',
|
|
'menunggu' => 'menunggu',
|
|
'ditolak' => 'tidak valid'
|
|
];
|
|
|
|
const REJECTION_REASONS_BY_CRITERIA = [
|
|
'Pekerjaan Orang Tua' => [
|
|
'Surat keterangan kerja tidak valid',
|
|
'Tidak ada bukti usaha (untuk wiraswasta)',
|
|
'Dokumen kadaluarsa',
|
|
'Dokumen tidak sesuai',
|
|
'Lainnya'
|
|
],
|
|
'Penghasilan Orang Tua' => [
|
|
'Jumlah gaji tidak sesuai',
|
|
'Slip gaji tidak terbaca',
|
|
'Dokumen tidak sesuai',
|
|
'Lainnya'
|
|
],
|
|
'Status Orang Tua' => [
|
|
'Dokumen tidak sesuai',
|
|
'Dokumen tidak terbaca',
|
|
'Tidak ada bukti surat perceraian',
|
|
'Tidak ada bukti surat keterangan kematian',
|
|
'Lainnya'
|
|
],
|
|
'Tanggungan Orang Tua' => [
|
|
'Kartu Keluarga tidak terbaca',
|
|
'Jumlah tanggungan tidak sesuai',
|
|
'Dokumen tidak sesuai',
|
|
'Lainnya'
|
|
],
|
|
'Hunian' => [
|
|
'Bukti kepemilikan tidak valid',
|
|
'Dokumen tidak terbaca',
|
|
'Dokumen tidak sesuai',
|
|
'Lainnya'
|
|
],
|
|
'Kendaraan' => [
|
|
'Dokumen tidak sesuai',
|
|
'STNK tidak valid',
|
|
'STNK tidak terbaca',
|
|
'Lainnya'
|
|
],
|
|
'IPK' => [
|
|
'KHS tidak valid',
|
|
'KHS tidak terbaca',
|
|
'IPK tidak sesuai dengan sistem',
|
|
'Lainnya'
|
|
],
|
|
'Semester' => [
|
|
'KHS tidak valid',
|
|
'KHS tidak terbaca',
|
|
'Semester tidak sesuai dengan sistem',
|
|
'Lainnya'
|
|
],
|
|
'Slip Pembayaran UKT' => [
|
|
'Dokumen tidak terbaca',
|
|
'Nominal tidak sesuai',
|
|
'Slip pembayaran tidak sesuai',
|
|
'Lainnya'
|
|
]
|
|
];
|
|
|
|
|
|
/**
|
|
* Display list of UKT submissions with filtering
|
|
*/
|
|
public function index()
|
|
{
|
|
$statusFilter = $this->mapStatusFilter(request('status'));
|
|
|
|
$query = PengajuanUkt::with(['mahasiswa.user:id,name','form'])
|
|
->withCount('details');
|
|
|
|
if (request()->has('status_form_id') && request('status_form_id') != '') {
|
|
$query->where('status_form_id', request('status_form_id'));
|
|
}
|
|
|
|
// Filter by jenis form
|
|
if (request()->has('jenis_form') && request('jenis_form') != '') {
|
|
$query->whereHas('form', function($q) {
|
|
$q->where('jenis_form', request('jenis_form'));
|
|
});
|
|
}
|
|
|
|
if ($statusFilter) {
|
|
$query->where('status_validasi', $statusFilter);
|
|
}
|
|
|
|
$pengajuan = $query->latest()->paginate(10);
|
|
|
|
$forms = Form::select('id', 'nama_form')
|
|
->orderBy('created_at', 'desc')
|
|
->get();
|
|
|
|
$stats = [
|
|
'totalPengajuan' => PengajuanUkt::count(),
|
|
'valid' => PengajuanUkt::valid()->count(),
|
|
'menunggu' => PengajuanUkt::menunggu()->count(),
|
|
'tidak valid' => PengajuanUkt::tidakValid()->count()
|
|
];
|
|
|
|
return view('admin.pengajuan.index', compact('pengajuan', 'stats', 'forms'));
|
|
}
|
|
|
|
/**
|
|
* Map status filter to consistent values
|
|
*/
|
|
protected function mapStatusFilter($status)
|
|
{
|
|
return self::STATUS_MAPPING[$status] ?? $status;
|
|
}
|
|
|
|
public function detail($id)
|
|
{
|
|
$pengajuan = PengajuanUkt::with([
|
|
'details' => function($query) {
|
|
$query->select([
|
|
'id',
|
|
'pengajuan_ukt_id',
|
|
'kriteria',
|
|
'subkriteria_text',
|
|
'file_dokumen',
|
|
'verified',
|
|
'rejection_reason',
|
|
'verified_by',
|
|
'verified_at'
|
|
])->with(['verifier:id,name']);
|
|
},
|
|
'mahasiswa.user:id,name'
|
|
])->select([
|
|
'id',
|
|
'mahasiswa_id',
|
|
'jenis_pengajuan',
|
|
'alasan_pengajuan',
|
|
'ukt_saat_ini',
|
|
'status_validasi',
|
|
'created_at',
|
|
'updated_at'
|
|
])->findOrFail($id);
|
|
|
|
return view('admin.pengajuan.detail', [
|
|
'pengajuan' => $pengajuan,
|
|
'rejectionReasonsByCriteria' => self::REJECTION_REASONS_BY_CRITERIA
|
|
]);
|
|
}
|
|
|
|
public function bulkVerify(Request $request, $id)
|
|
{
|
|
if (auth()->user()->role !== 'karyawan') {
|
|
abort(403, 'Hanya karyawan yang dapat melakukan verifikasi');
|
|
}
|
|
|
|
$data = $request->all();
|
|
|
|
$rules = [
|
|
'valid' => 'required|array',
|
|
'valid.*' => 'in:0,1',
|
|
'alasan' => 'nullable|array',
|
|
'alasan_lainnya' => 'nullable|array',
|
|
];
|
|
|
|
foreach ($data['valid'] as $detailId => $value) {
|
|
if ((string) $value === '0') {
|
|
$rules["alasan.$detailId"] = 'required|string';
|
|
if (isset($data['alasan'][$detailId]) && $data['alasan'][$detailId] === 'Lainnya') {
|
|
$rules["alasan_lainnya.$detailId"] = 'required|string';
|
|
}
|
|
}
|
|
}
|
|
|
|
$validator = Validator::make($data, $rules);
|
|
|
|
if ($validator->fails()) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Validasi gagal',
|
|
'errors' => $validator->errors()
|
|
], 422);
|
|
}
|
|
|
|
DB::beginTransaction();
|
|
|
|
try {
|
|
$pengajuan = PengajuanUkt::with(['details', 'mahasiswa.user'])->findOrFail($id);
|
|
|
|
foreach ($pengajuan->details as $detail) {
|
|
$isValid = $request->input("valid.{$detail->id}") == '1';
|
|
$alasan = $request->input("alasan.{$detail->id}");
|
|
$alasanLainnya = $request->input("alasan_lainnya.{$detail->id}");
|
|
|
|
$rejectionReason = $this->processRejectionReason($alasan, $alasanLainnya);
|
|
|
|
$detail->update([
|
|
'verified' => $isValid,
|
|
'verified_by' => auth()->id(),
|
|
'verified_at' => now(),
|
|
'rejection_reason' => $isValid ? null : $rejectionReason,
|
|
]);
|
|
}
|
|
|
|
$status = $this->updatePengajuanStatus($pengajuan);
|
|
|
|
DB::commit();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Validasi dokumen berhasil disimpan! Status: ' . ($status === 'valid' ? 'Valid' : 'Tidak Valid'),
|
|
'redirect' => route('admin.pengajuan.detail', $id)
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Gagal memvalidasi pengajuan: ' . $e->getMessage()
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
public function revalidate(Request $request, $id)
|
|
{
|
|
$validator = Validator::make($request->all(), [
|
|
'valid' => 'required|array',
|
|
'valid.*' => 'in:0,1',
|
|
'alasan' => 'required_without:valid.*|array',
|
|
'alasan_lainnya' => 'required_if:alasan.*,Lainnya|array'
|
|
]);
|
|
|
|
if ($validator->fails()) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Validasi gagal',
|
|
'errors' => $validator->errors()
|
|
], 422);
|
|
}
|
|
|
|
DB::beginTransaction();
|
|
|
|
try {
|
|
$pengajuan = PengajuanUkt::with('details')->findOrFail($id);
|
|
$changesMade = false;
|
|
|
|
foreach ($request->input('valid') as $detailId => $isValid) {
|
|
$detail = $pengajuan->details->firstWhere('id', $detailId);
|
|
if (!$detail) continue;
|
|
|
|
$alasan = $request->input("alasan.{$detailId}");
|
|
$alasanLainnya = $request->input("alasan_lainnya.{$detailId}");
|
|
$rejectionReason = $isValid ? null : $this->processRejectionReason($alasan, $alasanLainnya);
|
|
|
|
// Check if changes are needed
|
|
if ($detail->verified != $isValid || $detail->rejection_reason != $rejectionReason) {
|
|
$detail->verified = $isValid;
|
|
$detail->rejection_reason = $rejectionReason;
|
|
$detail->verified_by = auth()->id();
|
|
$detail->verified_at = now();
|
|
$detail->save();
|
|
$changesMade = true;
|
|
}
|
|
}
|
|
|
|
if ($changesMade) {
|
|
$status = $this->updatePengajuanStatus($pengajuan);
|
|
DB::commit();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Revalidasi berhasil disimpan',
|
|
'redirect' => route('admin.pengajuan.detail', $id)
|
|
]);
|
|
}
|
|
|
|
DB::commit();
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Tidak ada perubahan yang dilakukan',
|
|
'redirect' => route('admin.pengajuan.detail', $id)
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Gagal menyimpan revalidasi: ' . $e->getMessage()
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
public function verifyUkt(Request $request, $id)
|
|
{
|
|
$validator = Validator::make($request->all(), [
|
|
'valid_ukt' => 'required|boolean',
|
|
'alasan_ukt' => 'required_if:valid_ukt,0|nullable|string',
|
|
'alasan_lainnya_ukt' => 'required_if:alasan_ukt,Lainnya|nullable|string'
|
|
]);
|
|
|
|
if ($validator->fails()) {
|
|
return back()
|
|
->withErrors($validator)
|
|
->withInput();
|
|
}
|
|
|
|
DB::beginTransaction();
|
|
|
|
try {
|
|
$pengajuan = PengajuanUkt::with(['details', 'mahasiswa.user'])
|
|
->findOrFail($id);
|
|
|
|
$alasanUkt = $this->processRejectionReason(
|
|
$request->alasan_ukt,
|
|
$request->alasan_lainnya_ukt
|
|
);
|
|
|
|
$pengajuan->update([
|
|
'file_ukt_valid' => $request->valid_ukt,
|
|
'file_ukt_alasan' => $request->valid_ukt ? null : $alasanUkt,
|
|
'updated_at' => now()
|
|
]);
|
|
|
|
$this->updatePengajuanStatus($pengajuan);
|
|
|
|
DB::commit();
|
|
|
|
return redirect()
|
|
->route('admin.pengajuan.detail', $id)
|
|
->with('success', 'Verifikasi Slip UKT berhasil disimpan');
|
|
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
return back()
|
|
->withInput()
|
|
->with('error', 'Gagal memverifikasi Slip UKT: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
protected function processRejectionReason($alasan, $alasanLainnya)
|
|
{
|
|
if ($alasan === 'Lainnya') {
|
|
return 'Lainnya: ' . $alasanLainnya;
|
|
}
|
|
return $alasan;
|
|
}
|
|
|
|
protected function updatePengajuanStatus($pengajuan)
|
|
{
|
|
$invalidCount = $pengajuan->details()
|
|
->where('verified', false)
|
|
->orWhereNotNull('rejection_reason')
|
|
->count();
|
|
|
|
$status = $invalidCount === 0 ? 'valid' : 'tidak valid';
|
|
|
|
$pengajuan->update([
|
|
'status_validasi' => $status,
|
|
'updated_at' => now()
|
|
]);
|
|
|
|
$this->sendNotification($pengajuan, $status);
|
|
|
|
return $status;
|
|
}
|
|
|
|
private function sendNotification($pengajuan, $status)
|
|
{
|
|
try {
|
|
if ($status === 'valid') {
|
|
$pengajuan->mahasiswa->user->notify(new PengajuanValidNotification($pengajuan));
|
|
} else {
|
|
$reasons = $pengajuan->details()
|
|
->whereNotNull('rejection_reason')
|
|
->pluck('rejection_reason', 'kriteria')
|
|
->map(function($reason, $kriteria) {
|
|
return "$kriteria: " . (strpos($reason, 'Lainnya:') === 0 ? substr($reason, 8) : $reason);
|
|
})
|
|
->implode("\n");
|
|
|
|
$pengajuan->mahasiswa->user->notify(
|
|
new PengajuanDitolakNotification($pengajuan, $reasons)
|
|
);
|
|
}
|
|
} catch (\Exception $e) {
|
|
// Silent fail for notifications
|
|
}
|
|
}
|
|
|
|
public function destroy($id)
|
|
{
|
|
DB::beginTransaction();
|
|
try {
|
|
$pengajuan = PengajuanUkt::with(['details', 'hasilPenilaian'])->findOrFail($id);
|
|
|
|
foreach ($pengajuan->details as $detail) {
|
|
if ($detail->file_dokumen) {
|
|
$filePath = 'public/' . ltrim($detail->file_dokumen, '/');
|
|
if (Storage::exists($filePath)) {
|
|
Storage::delete($filePath);
|
|
}
|
|
}
|
|
}
|
|
|
|
$pengajuan->hasilPenilaian()->delete();
|
|
$pengajuan->details()->delete();
|
|
$pengajuan->delete();
|
|
|
|
DB::commit();
|
|
|
|
return redirect()->route('admin.pengajuan')
|
|
->with('success', 'Pengajuan berhasil dihapus');
|
|
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
return back()->with('error', 'Gagal menghapus pengajuan');
|
|
}
|
|
}
|
|
|
|
public function storeRankingReport(Request $request)
|
|
{
|
|
$request->validate([
|
|
'ranking_file' => 'required|file|mimes:pdf|max:10240'
|
|
]);
|
|
|
|
DB::beginTransaction();
|
|
try {
|
|
$existingFile = RankingFile::first();
|
|
if ($existingFile) {
|
|
Storage::delete('public/' . $existingFile->path);
|
|
$existingFile->delete();
|
|
}
|
|
|
|
$file = $request->file('ranking_file');
|
|
$path = $file->store('public/ranking_files');
|
|
|
|
RankingFile::create([
|
|
'path' => str_replace('public/', '', $path),
|
|
'uploaded_by' => auth()->id()
|
|
]);
|
|
|
|
DB::commit();
|
|
|
|
return redirect()->route('admin.pengajuan')
|
|
->with('report_status', [
|
|
'type' => 'success',
|
|
'message' => 'Laporan ranking berhasil dikirim ke mahasiswa!'
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
return back()->with('report_status', [
|
|
'type' => 'danger',
|
|
'message' => 'Gagal mengunggah laporan ranking: ' . $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function deleteRankingReport()
|
|
{
|
|
DB::beginTransaction();
|
|
try {
|
|
$rankingFile = RankingFile::first();
|
|
|
|
if ($rankingFile) {
|
|
Storage::delete('public/' . $rankingFile->path);
|
|
$rankingFile->delete();
|
|
|
|
$message = 'Laporan ranking berhasil dibatalkan!';
|
|
$type = 'warning';
|
|
} else {
|
|
$message = 'Tidak ada laporan ranking aktif';
|
|
$type = 'info';
|
|
}
|
|
|
|
DB::commit();
|
|
|
|
return redirect()->route('admin.pengajuan')
|
|
->with('report_status', [
|
|
'type' => $type,
|
|
'message' => $message
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
return back()->with('report_status', [
|
|
'type' => 'danger',
|
|
'message' => 'Gagal membatalkan laporan ranking: ' . $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function viewRankingReport($id)
|
|
{
|
|
$rankingFile = RankingFile::findOrFail($id);
|
|
|
|
$filePath = storage_path('app/public/' . $rankingFile->path);
|
|
|
|
if (!file_exists($filePath)) {
|
|
abort(404, 'File laporan tidak ditemukan');
|
|
}
|
|
|
|
return response()->download(
|
|
$filePath,
|
|
'Laporan-UKT-'.$rankingFile->created_at->format('Y-m-d').'.pdf'
|
|
);
|
|
}
|
|
} |