From 0ae3d82134f8dfec2f5a574c36dc56eace058efa Mon Sep 17 00:00:00 2001 From: seiyanz16 Date: Mon, 28 Apr 2025 14:59:58 +0700 Subject: [PATCH] feat: certificate export admin --- .../Controllers/CertificateController.php | 186 ++++++++++++++++++ app/Models/Certificate.php | 18 ++ ...04_20_115346_create_certificates_table.php | 33 ++++ resources/views/components/table.blade.php | 21 ++ resources/views/scores/index.blade.php | 179 +++++++++-------- routes/web.php | 3 + 6 files changed, 362 insertions(+), 78 deletions(-) create mode 100644 app/Http/Controllers/CertificateController.php create mode 100644 app/Models/Certificate.php create mode 100644 database/migrations/2025_04_20_115346_create_certificates_table.php diff --git a/app/Http/Controllers/CertificateController.php b/app/Http/Controllers/CertificateController.php new file mode 100644 index 0000000..fef838a --- /dev/null +++ b/app/Http/Controllers/CertificateController.php @@ -0,0 +1,186 @@ +input('user')); + $companyId = decrypt($request->input('company')); + + $user = User::findOrFail($userId); + $company = $user->companies()->findOrFail($companyId); + + return $this->generateAndDownloadNewCertificate($request, $user, $company); + } + + public function downloadCertificate(Request $request, $id) + { + $certificate = Certificate::findOrFail($id); + $user = User::findOrFail($certificate->user_id); + $company = $user->companies()->findOrFail($certificate->company_id); + + return $this->processDownloadExistingCertificate($user, $company, $certificate->certificate_number); + } + + private function getExistingCertificate($userId, $companyId) + { + return Certificate::where('user_id', $userId) + ->where('company_id', $companyId) + ->first(); + } + + private function processDownloadExistingCertificate($user, $company, $certificateNumber) + { + $formFields = $this->getCertificateFormFields($user, $company, $certificateNumber); + $scores = $user->scores()->where('company_id', $company->id)->get(); + + $fileName = now()->format('Y') . '_' . $user->departments->first()?->name. '_' . $user->name . '_certificate_' . '.pdf'; + $mergedPdfPath = $this->generateMergedPdf($formFields, $scores, $fileName); + + if ($mergedPdfPath) { + return response()->download($mergedPdfPath, $fileName)->deleteFileAfterSend(true); + } + + return redirect()->back()->with('error', 'Gagal membuat ulang sertifikat.'); + } + + private function generateAndDownloadNewCertificate(Request $request, $user, $company) + { + $noSertif = $request->input('certificate_number'); + $scores = $user->scores()->where('company_id', $company->id)->get(); + + if (empty($user->nis)) { + return redirect()->back()->with('error', 'NIS siswa belum terisi. Harap lengkapi data siswa terlebih dahulu.'); + } + + if ($scores->isEmpty()) { + return redirect()->back()->with('error', 'Nilai tidak ditemukan! Harap tambahkan nilai terlebih dahulu.'); + } + + $this->checkCertificate($user, $company->id, $noSertif); + + $formFields = $this->getCertificateFormFields($user, $company, $noSertif); + $fileName = now()->format('Y') . '_' . $user->departments->first()?->name . '_' . $user->name . '_certificate_' . '.pdf'; + $mergedPdfPath = $this->generateMergedPdf($formFields, $scores, $fileName); + + if ($mergedPdfPath) { + return response()->download($mergedPdfPath, $fileName)->deleteFileAfterSend(true); + } + + return redirect()->back()->with('error', 'Gagal membuat sertifikat.'); + } + + private function getCertificateFormFields($user, $company, $certificateNumber) + { + $internDate = $user->internDates()->where('company_id', $company->id)->first(); + $formattedDateOfBirth = $user->date_of_birth + ? Carbon::parse($user->date_of_birth)->translatedFormat('j F Y') + : 'N/A'; + + return [ + 'no_sertif' => 'No. ' . $certificateNumber, + 'nama_siswa' => $user->name, + 'ttl' => $formattedDateOfBirth, + 'nis' => $user->nis, + 'program_studi' => $user->departments->first()?->study_program, + 'jurusan' => $user->departments->first()?->description, + 'instansi_nama' => $company->name, + 'tgl_mulai' => $internDate ? Carbon::parse($internDate->start_date)->translatedFormat('j F Y') : 'N/A', + 'tgl_selesai' => $internDate ? Carbon::parse($internDate->end_date)->translatedFormat('j F Y') : 'N/A', + 'tgl_export' => Carbon::now()->translatedFormat('j F Y'), + 'instansi_direktur' => $company->contact_person, + 'nilai_all' => number_format($user->scores()->where('company_id', $company->id)->avg('score') ?? 0, 2), + ]; + } + + private function generateMergedPdf($formFields, $scores, $fileName) + { + $templatePath = public_path('template-pdf/template_certificate_infront.pdf'); + + if (!file_exists($templatePath)) { + \Log::error('Template PDF tidak ditemukan.'); + return null; + } + + $outputPathFront = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'front_' . $fileName; + $pdfFront = new Pdf($templatePath); + $resultFront = $pdfFront->fillForm($formFields)->flatten()->saveAs($outputPathFront); + + if (!$resultFront) { + \Log::error('Gagal membuat halaman depan PDF: ' . $pdfFront->getError()); + return null; + } + + $technicalScores = $scores->where('type', 'teknis'); + $nonTechnicalScores = $scores->where('type', 'non-teknis'); + + foreach ($technicalScores as $score) { + $score->letter = $this->convertScoreToLetter($score->score); + } + foreach ($nonTechnicalScores as $score) { + $score->letter = $this->convertScoreToLetter($score->score); + } + + $pdfBack = DomPdfFacade::loadView('pdf.certificate_back', [ + 'technicalScores' => $technicalScores, + 'nonTechnicalScores' => $nonTechnicalScores, + 'avgScore' => number_format($scores->avg('score') ?? 0, 2), + ])->setPaper('a4', 'landscape'); + + $outputPathBack = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'back_' . $fileName; + $pdfBack->save($outputPathBack); + + $mergedPdfPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $fileName; + $pdf = new Pdf(); + $mergeResult = $pdf->addFile($outputPathFront) + ->addFile($outputPathBack) + ->saveAs($mergedPdfPath); + + if (!$mergeResult) { + \Log::error('Gagal menggabungkan PDF: ' . $pdf->getError()); + @unlink($outputPathFront); + @unlink($outputPathBack); + return null; + } + + return $mergedPdfPath; + } + + private function convertScoreToLetter($score) + { + if ($score >= 90) { + return 'Sangat Baik'; + } elseif ($score >= 75) { + return 'Baik'; + } elseif ($score >= 60) { + return 'Cukup'; + } else { + return 'Kurang'; + } + } + + private function checkCertificate($user, $companyId, $noSertif) + { + $existingCert = Certificate::where('user_id', $user->id) + ->where('company_id', $companyId) + ->first(); + + if (!$existingCert) { + Certificate::create([ + 'user_id' => $user->id, + 'company_id' => $companyId, + 'department_id' => $user->departments->first()?->id, + 'certificate_number' => $noSertif + ]); + } + } +} diff --git a/app/Models/Certificate.php b/app/Models/Certificate.php new file mode 100644 index 0000000..51f7864 --- /dev/null +++ b/app/Models/Certificate.php @@ -0,0 +1,18 @@ +id(); + $table->foreignId('user_id')->constrained('users')->cascadeOnDelete()->cascadeOnUpdate(); + $table->foreignId('department_id')->constrained('departments')->cascadeOnDelete()->cascadeOnUpdate(); + $table->foreignId('company_id')->constrained('companies')->cascadeOnDelete()->cascadeOnUpdate(); + $table->string('certificate_number')->unique(); + $table->timestamps(); + + $table->unique(['user_id', 'company_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('certificates'); + } +}; diff --git a/resources/views/components/table.blade.php b/resources/views/components/table.blade.php index 81315e2..9bc2655 100644 --- a/resources/views/components/table.blade.php +++ b/resources/views/components/table.blade.php @@ -47,6 +47,27 @@ APPROVE ALL @endif + + + @if ($routeCreate && request()->is('scores')) + @php + $certificate = \App\Models\Certificate::where('user_id', decrypt(request()->query('user'))) + ->where('company_id', decrypt(request()->query('company'))) + ->first(); + $hasCertificate = !empty($certificate); + @endphp + @if ($hasCertificate) + + Unduh Sertifikat + + @else + + @endif + @endif
diff --git a/resources/views/scores/index.blade.php b/resources/views/scores/index.blade.php index b9237e1..e55a620 100644 --- a/resources/views/scores/index.blade.php +++ b/resources/views/scores/index.blade.php @@ -6,60 +6,83 @@ @section('title', 'Penilaian PKL ' . $userName) @section('dashboard-content') - + - - - Kelola - - - Subyek - - - Tipe - - - Nilai - - - Predikat - - + + + Kelola + + + Subyek + + + Tipe + + + Nilai + + + Predikat + + - - @foreach ($scores as $data) - - - @can('score-edit') - - @endcan - @can('score-delete') -
- @csrf - @method('DELETE') - -
- @endcan - - {{ $data->name }} - {{ $data->type === 'teknis' ? 'Teknis' : 'Non Teknis' }} - {{ $data->score }} - - - {{ $data->score_predicate->name }} - - + + @foreach ($scores as $data) + + + @can('score-edit') + + @endcan + @can('score-delete') +
+ @csrf + @method('DELETE') + +
+ @endcan + + {{ $data->name }} + {{ $data->type === 'teknis' ? 'Teknis' : 'Non Teknis' }} + {{ $data->score }} + + + {{ $data->score_predicate->name }} + + @endforeach -
-
-
- - -
+ +
+
+ + +
+ + + +
+ @csrf + + +
+ + +
+
+
+ + + + + + +
+ @endsection @push('scripts') @@ -67,33 +90,33 @@ class="bi bi-trash"> $(document).ready(function() { $('#approve').on('click', function() { window - .swal({ - title: "Apakah anda yakin?", - text: "Anda akan menyetujui tindakan ini", - icon: "warning", - buttons: { - cancel: { - text: "Batal", - value: null, - visible: true, - className: "btn btn-primary", - closeModal: true, - }, - confirm: { - text: "Setuju", - value: true, - visible: true, - className: "btn btn-success", - closeModal: true, - }, - }, - }) - .then((value) => { - if (value) { - $('#formApprove').trigger('submit'); - } - }); + .swal({ + title: "Apakah anda yakin?", + text: "Anda akan menyetujui tindakan ini", + icon: "warning", + buttons: { + cancel: { + text: "Batal", + value: null, + visible: true, + className: "btn btn-primary", + closeModal: true, + }, + confirm: { + text: "Setuju", + value: true, + visible: true, + className: "btn btn-success", + closeModal: true, + }, + }, + }) + .then((value) => { + if (value) { + $('#formApprove').trigger('submit'); + } + }); }); }); -@endpush +@endpush \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 370f83c..27e7d32 100644 --- a/routes/web.php +++ b/routes/web.php @@ -25,6 +25,7 @@ use App\Http\Controllers\PresenceStatusController; use App\Http\Controllers\ScorePredicateController; use App\Exports\StudentsExport; +use App\Http\Controllers\CertificateController; use App\Http\Controllers\FaqController; use App\Http\Controllers\TeacherController; use Maatwebsite\Excel\Facades\Excel; @@ -123,6 +124,8 @@ Route::put('reviews/users', [ReviewController::class, 'userUpdate'])->name('reviews.users.update'); Route::resource('scores', ScoreController::class); + Route::post('scores/export-certificate', [CertificateController::class, 'exportCertificate'])->name('export-certificate'); + Route::get('scores/download-certificate/{id}', [CertificateController::class, 'downloadCertificate'])->name('certificate.download'); Route::get('edit-profile', [UserController::class, 'editProfile'])->name('users.editProfile'); Route::put('update-profile', [UserController::class, 'updateProfile'])->name('users.updateProfile');