From 274addb06944430f217b4139586c484e03870a9a Mon Sep 17 00:00:00 2001 From: Daeng Deni Mardaeni Date: Thu, 17 Jul 2025 09:41:20 +0700 Subject: [PATCH] refactor(memo): Sesuaikan struktur form dan tambahkan kalkulasi Total Biaya PJ Melakukan refactor dan penyesuaian form memo penyelesaian agar lebih sesuai dengan kebutuhan bisnis, serta menambahkan fitur perhitungan otomatis Total Biaya PJ. Perubahan pada Form Input: - Mengubah field "Judul Memo" menjadi "Nomor Memo" untuk identifikasi memo yang lebih spesifik. - Menghapus field "Isi Memo" karena tidak relevan dengan proses bisnis saat ini. - Menambahkan field "Tanggal Pembayaran" untuk tracking proses pembayaran. - Mengatur "Tanggal Memo" menjadi otomatis mengikuti tanggal hari ini dan disimpan sebagai hidden field untuk keperluan audit. - Menambahkan field readonly "Total Biaya PJ" untuk menampilkan akumulasi biaya dari NOC terkait permohonan yang dipilih. Perubahan pada Controller: - Mengupdate validasi request agar sesuai dengan field baru: `memo_number`, `payment_date`, dan `permohonan_ids`. - Menghapus validasi `memo_content` karena field tersebut tidak lagi digunakan. - Menambahkan method `getTotalBiayaPJ()` untuk endpoint AJAX yang menghitung total biaya PJ secara real-time. - Menggunakan relasi model `Noc` dan `Permohonan` untuk menghitung sum dari `nominal_bayar`. - Mengupdate method `create()` agar langsung menghitung total biaya PJ saat form dibuka. - Tetap menggunakan DB transaction untuk memastikan integritas data. Perubahan pada Database Schema: - Mengganti field `memo_penyelesaian_title` menjadi `memo_penyelesaian_number`. - Menghapus field `memo_penyelesaian_content`. - Menambahkan field baru `memo_penyelesaian_payment_date` untuk menyimpan tanggal pembayaran. - Mempertahankan field `memo_penyelesaian_date` sebagai audit trail. Perubahan pada View: - Menambahkan field readonly "Total Biaya PJ" dengan format mata uang Rupiah. - Menambahkan icon kalkulator dan styling sesuai dengan tema form. - Menggunakan AJAX untuk menghitung total biaya PJ ketika user memilih atau mengubah permohonan secara dinamis. - Menampilkan pesan error dan feedback user secara jelas jika terjadi masalah saat perhitungan. Routing dan API: - Menambahkan route `memo.total-biaya-pj` sebagai endpoint untuk kalkulasi biaya PJ berbasis AJAX. - Tetap menggunakan route resource untuk operasi CRUD memo penyelesaian. Keamanan dan Validasi: - Implementasi CSRF protection untuk AJAX request. - Validasi `permohonan_ids` harus berupa array yang valid dan terfilter dengan baik. - Penanganan error yang komprehensif baik di sisi controller maupun client-side. Peningkatan User Experience: - Form menjadi lebih sederhana, efisien, dan fokus pada input yang memang dibutuhkan oleh proses bisnis. - Real-time feedback saat memilih permohonan sehingga user langsung mengetahui total biaya PJ. - Layout form tetap responsive dan mudah digunakan di berbagai perangkat. - Memberikan pengalaman yang konsisten dengan desain aplikasi lainnya. Tujuan Perubahan: - Menyederhanakan proses pembuatan memo penyelesaian sesuai kebutuhan operasional terbaru. - Memastikan proses input lebih cepat dan akurat dengan kalkulasi otomatis Total Biaya PJ. - Mengurangi potensi kesalahan input dan mempercepat workflow divisi terkait. - Meningkatkan maintainability dan konsistensi kode dengan standar sistem yang ada. --- app/Http/Controllers/MemoController.php | 131 ++++++++++++++---- resources/views/memo/create.blade.php | 177 ++++++++++++++++-------- resources/views/memo/index.blade.php | 4 + resources/views/memo/show.blade.php | 173 ++++++++++++----------- routes/breadcrumbs.php | 17 +++ routes/web.php | 9 +- 6 files changed, 339 insertions(+), 172 deletions(-) diff --git a/app/Http/Controllers/MemoController.php b/app/Http/Controllers/MemoController.php index 41efdc0..b38c09e 100644 --- a/app/Http/Controllers/MemoController.php +++ b/app/Http/Controllers/MemoController.php @@ -2,12 +2,13 @@ namespace Modules\Lpj\Http\Controllers; -use App\Http\Controllers\Controller; +use Exception; +use Modules\Lpj\Models\Noc; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Log; use Modules\Lpj\Models\Permohonan; -use Exception; +use Illuminate\Support\Facades\Log; +use App\Http\Controllers\Controller; class MemoController extends Controller { @@ -34,33 +35,49 @@ class MemoController extends Controller { Log::info('MemoController: Mengakses halaman create memo penyelesaian'); - // Tambahkan validasi di awal method - $selectedIds = $request->get('selected_ids', []); - $selectedIds = explode(',', $selectedIds); - $permohonanList = []; - if (!empty($selectedIds)) { + // Pastikan $selectedIds selalu berupa array + if (is_string($selectedIds)) { + $selectedIds = explode(',', $selectedIds); + } + + // Filter array untuk menghilangkan nilai kosong + $selectedIds = array_filter($selectedIds, function($id) { + return !empty(trim($id)); + }); + + $permohonanList = []; + $totalBiayaPJ = 0; + + if (!empty($selectedIds) && count($selectedIds) > 0) { try { - $permohonanList = Permohonan::with([ - 'user', - 'debiture', - 'branch', - 'tujuanPenilaian', - 'penilaian', - 'jenisFasilitasKredit', - 'documents.inspeksi', - 'penilai', - 'documents.detail', - 'noc' - ])->whereIn('id', $selectedIds)->get(); + $permohonanList = Permohonan::with([ + 'user', + 'debiture', + 'branch', + 'tujuanPenilaian', + 'penilaian', + 'jenisFasilitasKredit', + 'documents.inspeksi', + 'penilai', + 'documents.detail', + 'noc' + ])->whereIn('id', $selectedIds)->get(); + + // Hitung total biaya PJ dari nominal_bayar di tabel NOC + $totalBiayaPJ = Noc::whereIn('permohonan_id', $selectedIds) + ->sum('nominal_bayar'); + + Log::info('MemoController: Total Biaya PJ dihitung: ' . $totalBiayaPJ); + } catch (Exception $e) { Log::error('MemoController: Error saat mengambil data permohonan - ' . $e->getMessage()); return redirect()->back()->with('error', 'Terjadi kesalahan saat memuat data'); } } - return view('lpj::memo.create', compact('permohonanList')); + return view('lpj::memo.create', compact('permohonanList', 'totalBiayaPJ')); } /** @@ -80,17 +97,30 @@ class MemoController extends Controller $request->validate([ 'permohonan_ids' => 'required|array', 'permohonan_ids.*' => 'exists:permohonan,id', - 'memo_title' => 'required|string|max:255', - 'memo_content' => 'required|string', + 'memo_number' => 'required|string|max:255', + 'payment_date' => 'required|date', 'memo_date' => 'required|date' ]); $permohonanIds = $request->permohonan_ids; - $memoTitle = $request->memo_title; - $memoContent = $request->memo_content; + $memoNumber = $request->memo_number; + $paymentDate = $request->payment_date; $memoDate = $request->memo_date; - dd($request->all()); + // Update status permohonan yang dipilih + foreach ($permohonanIds as $permohonanId) { + $permohonan = Permohonan::find($permohonanId); + if ($permohonan) { + $permohonan->status = 'memo-penyelesaian'; + $permohonan->memo_penyelesaian_number = $memoNumber; + $permohonan->memo_penyelesaian_date = $memoDate; + $permohonan->memo_penyelesaian_payment_date = $paymentDate; + $permohonan->memo_penyelesaian_created_at = now(); + $permohonan->save(); + + Log::info('MemoController: Berhasil update permohonan ID: ' . $permohonanId); + } + } DB::commit(); Log::info('MemoController: Berhasil menyimpan memo penyelesaian untuk ' . count($permohonanIds) . ' permohonan'); @@ -235,4 +265,53 @@ class MemoController extends Controller 'data' => $data, ]); } + + /** + * Mengambil total biaya PJ berdasarkan permohonan yang dipilih + * + * @param Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function getTotalBiayaPJ(Request $request) + { + Log::info('MemoController: Mengambil total biaya PJ'); + + try { + $permohonanIds = $request->get('permohonan_ids', []); + + // Pastikan $permohonanIds selalu berupa array + if (is_string($permohonanIds)) { + $permohonanIds = explode(',', $permohonanIds); + } + + // Filter array untuk menghilangkan nilai kosong + $permohonanIds = array_filter($permohonanIds, function($id) { + return !empty(trim($id)); + }); + + $totalBiayaPJ = 0; + + if (!empty($permohonanIds) && count($permohonanIds) > 0) { + // Hitung total biaya PJ dari nominal_bayar di tabel NOC + $totalBiayaPJ = \Modules\Lpj\Models\Noc::whereIn('permohonan_id', $permohonanIds) + ->sum('nominal_bayar'); + } + + Log::info('MemoController: Total Biaya PJ berhasil dihitung: ' . $totalBiayaPJ); + + return response()->json([ + 'success' => true, + 'total_biaya_pj' => $totalBiayaPJ, + 'total_biaya_pj_formatted' => 'Rp ' . number_format($totalBiayaPJ, 0, ',', '.') + ]); + + } catch (Exception $e) { + Log::error('MemoController: Error saat menghitung total biaya PJ - ' . $e->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan saat menghitung total biaya PJ' + ], 500); + } + } } diff --git a/resources/views/memo/create.blade.php b/resources/views/memo/create.blade.php index a471ea7..9e94178 100644 --- a/resources/views/memo/create.blade.php +++ b/resources/views/memo/create.blade.php @@ -1,5 +1,10 @@ @extends('layouts.main') + +@section('breadcrumbs') + {{ Breadcrumbs::render('memo.create') }} +@endsection + @section('content')
@@ -8,16 +13,16 @@

Buat Memo Penyelesaian

- - +
- @if(session('error')) + @if (session('error'))
{{ session('error') }}
@@ -25,46 +30,54 @@
@csrf - + -
+
- - - @error('memo_title') - {{ $message }} + + + @error('memo_number') + {{ $message }} @enderror
- +
- - - @error('memo_date') - {{ $message }} + + + @error('payment_date') + {{ $message }} @enderror
+ +
+ +
+ +
+ +
+
+ Total biaya dari nominal bayar NOC untuk permohonan yang + dipilih +
- -
- - - @error('memo_content') - {{ $message }} - @enderror -
- + + + + - @if(count($permohonanList) > 0) + @if (count($permohonanList) > 0)
-

Daftar Permohonan yang Akan Diproses

+

Daftar Permohonan yang Akan Diproses

- @@ -76,19 +89,19 @@ - @foreach($permohonanList as $permohonan) + @foreach ($permohonanList as $permohonan) @@ -106,21 +119,22 @@
+ Nomor Registrasi
- + {{ $permohonan->nomor_registrasi }} {{ $permohonan->debiture->name ?? '-' }} {{ $permohonan->branch->name ?? '-' }} {{ $permohonan->user->name ?? '-' }} - @if($permohonan->tujuanPenilaian) + @if ($permohonan->tujuanPenilaian) {{ $permohonan->tujuanPenilaian->name }} @@ -97,7 +110,7 @@ @endif - + {{ $permohonan->status }}
- -
-
- - Informasi: + +
+
+ + Informasi:
-

- Total {{ count($permohonanList) }} permohonan akan diproses untuk memo penyelesaian. +

+ Total {{ count($permohonanList) }} permohonan akan + diproses untuk memo penyelesaian. Anda dapat menghapus centang pada permohonan yang tidak ingin diproses.

- + -
+
Batal @@ -130,12 +144,12 @@
@else -
+
- +
-

Tidak Ada Data

-

Tidak ada permohonan yang dipilih untuk diproses.

+

Tidak Ada Data

+

Tidak ada permohonan yang dipilih untuk diproses.

Kembali ke Daftar @@ -155,6 +169,47 @@ const selectedCountSpan = document.getElementById('selected-count'); const submitBtn = document.getElementById('submit-btn'); const memoForm = document.getElementById('memo-form'); + const totalBiayaPJInput = document.getElementById('total-biaya-pj'); + + /** + * Fungsi untuk mengupdate total biaya PJ + */ + function updateTotalBiayaPJ() { + const checkedBoxes = document.querySelectorAll('.permohonan-checkbox:checked'); + const permohonanIds = Array.from(checkedBoxes).map(cb => cb.value); + + if (permohonanIds.length === 0) { + if (totalBiayaPJInput) { + totalBiayaPJInput.value = 'Rp 0'; + } + return; + } + + // Kirim AJAX request untuk mendapatkan total biaya PJ + fetch('{{ route('memo.total-biaya-pj') }}', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute( + 'content') + }, + body: JSON.stringify({ + permohonan_ids: permohonanIds + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success && totalBiayaPJInput) { + totalBiayaPJInput.value = data.total_biaya_pj_formatted; + } + }) + .catch(error => { + console.error('Error:', error); + if (totalBiayaPJInput) { + totalBiayaPJInput.value = 'Error menghitung total'; + } + }); + } /** * Fungsi untuk mengupdate jumlah item yang dipilih @@ -162,11 +217,11 @@ function updateSelectedCount() { const checkedBoxes = document.querySelectorAll('.permohonan-checkbox:checked'); const count = checkedBoxes.length; - + if (selectedCountSpan) { selectedCountSpan.textContent = count; } - + // Disable submit button jika tidak ada yang dipilih if (submitBtn) { submitBtn.disabled = count === 0; @@ -174,16 +229,20 @@ submitBtn.innerHTML = ' Pilih minimal 1 permohonan'; submitBtn.classList.add('btn-disabled'); } else { - submitBtn.innerHTML = ` Buat Memo Penyelesaian (${count} item)`; + submitBtn.innerHTML = + ` Buat Memo Penyelesaian (${count} item)`; submitBtn.classList.remove('btn-disabled'); } } - + // Update select all checkbox state if (selectAllCheckbox) { selectAllCheckbox.checked = count === permohonanCheckboxes.length; selectAllCheckbox.indeterminate = count > 0 && count < permohonanCheckboxes.length; } + + // Update total biaya PJ + updateTotalBiayaPJ(); } /** @@ -214,20 +273,22 @@ if (memoForm) { memoForm.addEventListener('submit', function(e) { const checkedBoxes = document.querySelectorAll('.permohonan-checkbox:checked'); - + if (checkedBoxes.length === 0) { e.preventDefault(); alert('Pilih minimal 1 permohonan untuk diproses!'); return false; } - + // Konfirmasi sebelum submit - const confirmed = confirm(`Apakah Anda yakin ingin membuat memo penyelesaian untuk ${checkedBoxes.length} permohonan?`); + const confirmed = confirm( + `Apakah Anda yakin ingin membuat memo penyelesaian untuk ${checkedBoxes.length} permohonan?` + ); if (!confirmed) { e.preventDefault(); return false; } - + // Disable submit button untuk mencegah double submit if (submitBtn) { submitBtn.disabled = true; @@ -236,8 +297,8 @@ }); } - // Initialize count + // Initialize count dan total biaya PJ updateSelectedCount(); }); -@endpush \ No newline at end of file +@endpush diff --git a/resources/views/memo/index.blade.php b/resources/views/memo/index.blade.php index 88091af..32d2825 100644 --- a/resources/views/memo/index.blade.php +++ b/resources/views/memo/index.blade.php @@ -1,5 +1,9 @@ @extends('layouts.main') +@section('breadcrumbs') + {{ Breadcrumbs::render('memo.index') }} +@endsection + @section('content')
@@ -8,7 +12,7 @@

Detail Memo Penyelesaian

-
+
Kembali @@ -23,26 +27,26 @@
-
-

+
+

Informasi Memo Penyelesaian

- +
-
+
-

+

{{ $permohonan->memo_penyelesaian_title ?? 'Belum ada memo' }}

- +
-

- @if($permohonan->memo_penyelesaian_date) +

+ @if ($permohonan->memo_penyelesaian_date) {{ \Carbon\Carbon::parse($permohonan->memo_penyelesaian_date)->format('d F Y') }} @else - @@ -50,23 +54,23 @@

- +

- @if($permohonan->status === 'memo-penyelesaian') - {{ $permohonan->status }} + @if ($permohonan->status === 'memo-penyelesaian') + {{ $permohonan->status }} @else - {{ $permohonan->status }} + {{ $permohonan->status }} @endif

- +
-

- @if($permohonan->memo_penyelesaian_created_at) +

+ @if ($permohonan->memo_penyelesaian_created_at) {{ \Carbon\Carbon::parse($permohonan->memo_penyelesaian_created_at)->format('d F Y H:i') }} @else - @@ -75,12 +79,12 @@

- - @if($permohonan->memo_penyelesaian_content) + + @if ($permohonan->memo_penyelesaian_content)
- +
-
+
{!! nl2br(e($permohonan->memo_penyelesaian_content)) !!}
@@ -91,68 +95,68 @@
-
-

+
+

Detail Permohonan

- +
-
+
-

{{ $permohonan->nomor_registrasi }}

+

{{ $permohonan->nomor_registrasi }}

- +
-

{{ $permohonan->debiture->name ?? '-' }}

+

{{ $permohonan->debiture->name ?? '-' }}

- +
-

{{ $permohonan->branch->name ?? '-' }}

+

{{ $permohonan->branch->name ?? '-' }}

- +
-

{{ $permohonan->user->name ?? '-' }}

+

{{ $permohonan->user->name ?? '-' }}

- +

- @if($permohonan->tujuanPenilaian) + @if ($permohonan->tujuanPenilaian) {{ $permohonan->tujuanPenilaian->name }} @else - @endif

- +
-

{{ $permohonan->jenisFasilitasKredit->name ?? '-' }}

+

{{ $permohonan->jenisFasilitasKredit->name ?? '-' }}

- +
-

- @if($permohonan->tanggal_permohonan) +

+ @if ($permohonan->tanggal_permohonan) {{ \Carbon\Carbon::parse($permohonan->tanggal_permohonan)->format('d F Y') }} @else - @endif

- - @if($permohonan->nilai_liquidasi) + + @if ($permohonan->nilai_liquidasi)
-

+

Rp {{ number_format($permohonan->nilai_liquidasi, 0, ',', '.') }}

@@ -163,41 +167,42 @@
- @if($permohonan->penilaian) + @if ($permohonan->penilaian)
-
-

+
+

Informasi Penilaian

- +
-
+
- @if($permohonan->penilaian->waktu_penilaian) + @if ($permohonan->penilaian->waktu_penilaian)
-

+

{{ \Carbon\Carbon::parse($permohonan->penilaian->waktu_penilaian)->format('d F Y H:i') }}

@endif - - @if($permohonan->penilai) + + @if ($permohonan->penilai)

- {{ $permohonan->penilai->type_penilai ?? '-' }} + {{ $permohonan->penilai->type_penilai ?? '-' }}

@endif
- +
- @if($permohonan->penilaian->created_at) + @if ($permohonan->penilaian->created_at)
-

+

{{ \Carbon\Carbon::parse($permohonan->penilaian->created_at)->format('d F Y H:i') }}

@@ -210,25 +215,25 @@
-
-

+
+

Status Approval

- +
-
-
+
+
- @if($permohonan->approval_so_at) - + @if ($permohonan->approval_so_at) + @else - + @endif
Senior Officer
-

- @if($permohonan->approval_so_at) +

+ @if ($permohonan->approval_so_at) Disetujui pada
{{ \Carbon\Carbon::parse($permohonan->approval_so_at)->format('d F Y H:i') }} @else @@ -236,18 +241,18 @@ @endif

- -
+ +
- @if($permohonan->approval_eo_at) - + @if ($permohonan->approval_eo_at) + @else - + @endif
Executive Officer
-

- @if($permohonan->approval_eo_at) +

+ @if ($permohonan->approval_eo_at) Disetujui pada
{{ \Carbon\Carbon::parse($permohonan->approval_eo_at)->format('d F Y H:i') }} @else @@ -255,18 +260,18 @@ @endif

- -
+ +
- @if($permohonan->approval_dd_at) - + @if ($permohonan->approval_dd_at) + @else - + @endif
Deputy Director
-

- @if($permohonan->approval_dd_at) +

+ @if ($permohonan->approval_dd_at) Disetujui pada
{{ \Carbon\Carbon::parse($permohonan->approval_dd_at)->format('d F Y H:i') }} @else @@ -283,24 +288,26 @@ @push('styles') -@endpush \ No newline at end of file +@endpush diff --git a/routes/breadcrumbs.php b/routes/breadcrumbs.php index e890842..8ac7616 100644 --- a/routes/breadcrumbs.php +++ b/routes/breadcrumbs.php @@ -786,5 +786,22 @@ Breadcrumbs::for('laporan-debiture', function ($trail) { Breadcrumbs::for('laporan-sla-penilai', function ($trail) { $trail->push('Laporan SLA Penilai', route('laporan-sla-penilai.index')); }); + + +Breadcrumbs::for('memo.index', function (BreadcrumbTrail $trail) { + $trail->push('Memo Penyelesaian', route('memo.index')); +}); + +Breadcrumbs::for('memo.show', function (BreadcrumbTrail $trail) { + $trail->push('Memo Penyelesaian', route('memo.index')); +}); + +Breadcrumbs::for('memo.create', function (BreadcrumbTrail $trail) { + $trail->parent('memo.index'); + $trail->push('Create', route('memo.index')); +}); + + + // add andy require __DIR__ . '/breadcrumbs_registrasi.php'; diff --git a/routes/web.php b/routes/web.php index f20a25d..c9213c4 100644 --- a/routes/web.php +++ b/routes/web.php @@ -398,14 +398,13 @@ Route::middleware(['auth'])->group(function () { }); Route::resource('laporan', LaporanController::class); - // Memo Penyelesaian routes - Route::name('memo.')->prefix('memo')->group(function () { - Route::get('datatables', [MemoController::class, 'dataForDatatables'])->name('datatables'); - Route::post('create-bulk', [MemoController::class, 'createBulk'])->name('create.bulk'); + Route::group(['prefix' => 'memo'], function () { + Route::get('datatables', [MemoController::class, 'dataForDatatables'])->name('memo.datatables'); + Route::get('create-bulk', [MemoController::class, 'create'])->name('memo.create.bulk'); + Route::post('total-biaya-pj', [MemoController::class, 'getTotalBiayaPJ'])->name('memo.total-biaya-pj'); }); Route::resource('memo', MemoController::class); - Route::name('resume.')->prefix('resume')->group(function () { Route::get('/', [ResumeController::class, 'index'])->name('index'); Route::get('{id}/show', [ResumeController::class, 'show'])->name('show');