feat(memo): Tambah halaman preview dan PDF memo penyelesaian
Menambahkan fitur preview memo penyelesaian sebelum data disimpan ke database, untuk memastikan user dapat memeriksa kembali detail permohonan dan biaya terkait. Perubahan pada Controller: - Menambahkan method `preview()` di MemoController untuk menampilkan halaman preview memo penyelesaian. - Menambahkan method `generatePdf()` untuk menghasilkan PDF resmi memo dengan format sesuai standar bank. - Mengimplementasikan validasi input lengkap sebelum proses preview dan PDF generation. - Mengintegrasikan helper fungsi terbilang untuk konversi total biaya ke dalam format huruf. Perubahan pada View: - Menambahkan view `preview.blade.php` sebagai template resmi memo penyelesaian dengan layout AGI header, informasi memo, daftar permohonan, total biaya PJ, dan tanda tangan. - Menampilkan tabel daftar permohonan terlampir dengan informasi debitur, cabang, AO, dan nominal biaya PJ. - Menyediakan fitur print preview dengan styling khusus untuk pencetakan dokumen resmi. - Menambahkan UI interaktif untuk mempermudah navigasi sebelum menyimpan data. Perubahan pada Form Create: - Mengubah alur form `create.blade.php` agar mengarahkan ke halaman preview terlebih dahulu sebelum penyimpanan. - Menambahkan tombol untuk melanjutkan proses ke simpan atau kembali ke pengisian form. Routing dan Navigasi: - Menambahkan route baru `memo.preview` untuk preview dan `memo.generate-pdf` untuk generate PDF memo penyelesaian. - Menambahkan breadcrumb untuk halaman preview agar navigasi lebih jelas. Fitur Tambahan: - Implementasi JavaScript untuk interaksi form preview, print action, dan konfirmasi user. - Penambahan fitur terbilang untuk memudahkan verifikasi nominal dalam format teks. - Penanganan error secara komprehensif dengan logging dan rollback pada transaksi jika terjadi kesalahan. - Menjamin data tetap konsisten meskipun user hanya melakukan preview tanpa menyimpan. Tujuan Perubahan: - Memberikan fasilitas preview agar user dapat memverifikasi data sebelum menyimpan memo penyelesaian. - Memastikan output memo dalam format PDF resmi yang sesuai dengan template bank. - Meningkatkan user experience dan mengurangi potensi kesalahan input sebelum proses finalisasi. - Mempermudah proses cetak memo dengan fitur print-friendly dan PDF yang siap dikirim atau diarsipkan.
This commit is contained in:
@@ -314,4 +314,147 @@ class MemoController extends Controller
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Menampilkan preview memo penyelesaian sebelum menyimpan
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function preview(Request $request)
|
||||
{
|
||||
Log::info('MemoController: Mengakses halaman preview memo penyelesaian');
|
||||
|
||||
// Validasi input
|
||||
$request->validate([
|
||||
'permohonan_ids' => 'required|array',
|
||||
'permohonan_ids.*' => 'exists:permohonan,id',
|
||||
'memo_number' => 'required|string|max:255',
|
||||
'payment_date' => 'required|date',
|
||||
'memo_date' => 'required|date'
|
||||
]);
|
||||
|
||||
$permohonanIds = $request->permohonan_ids;
|
||||
$memoNumber = $request->memo_number;
|
||||
$paymentDate = $request->payment_date;
|
||||
$memoDate = $request->memo_date;
|
||||
|
||||
try {
|
||||
// Ambil data permohonan yang dipilih
|
||||
$permohonanList = Permohonan::with([
|
||||
'user',
|
||||
'debiture',
|
||||
'branch',
|
||||
'tujuanPenilaian',
|
||||
'penilaian',
|
||||
'jenisFasilitasKredit',
|
||||
'documents.inspeksi',
|
||||
'penilai',
|
||||
'documents.detail',
|
||||
'noc'
|
||||
])->whereIn('id', $permohonanIds)->get();
|
||||
|
||||
// Hitung total biaya PJ dari nominal_bayar di tabel NOC
|
||||
$totalBiayaPJ = Noc::whereIn('permohonan_id', $permohonanIds)
|
||||
->sum('nominal_bayar');
|
||||
|
||||
// Data untuk template memo
|
||||
$memoData = [
|
||||
'memo_number' => $memoNumber,
|
||||
'memo_date' => $memoDate,
|
||||
'payment_date' => $paymentDate,
|
||||
'total_biaya_pj' => $totalBiayaPJ,
|
||||
'permohonan_list' => $permohonanList,
|
||||
'debitur_count' => $permohonanList->count(),
|
||||
'jaminan_info' => $this->getJaminanInfo($permohonanList)
|
||||
];
|
||||
|
||||
Log::info('MemoController: Data preview memo berhasil disiapkan');
|
||||
|
||||
return view('lpj::memo.preview', compact('memoData', 'permohonanList', 'totalBiayaPJ'));
|
||||
|
||||
} catch (Exception $e) {
|
||||
Log::error('MemoController: Error saat menyiapkan preview memo - ' . $e->getMessage());
|
||||
return redirect()->back()
|
||||
->withInput()
|
||||
->with('error', 'Terjadi kesalahan saat menyiapkan preview memo: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate PDF memo penyelesaian dan simpan ke database
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function generatePdf(Request $request)
|
||||
{
|
||||
Log::info('MemoController: Memulai generate PDF memo penyelesaian');
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
// Validasi input
|
||||
$request->validate([
|
||||
'permohonan_ids' => 'required|array',
|
||||
'permohonan_ids.*' => 'exists:permohonan,id',
|
||||
'memo_number' => 'required|string|max:255',
|
||||
'payment_date' => 'required|date',
|
||||
'memo_date' => 'required|date'
|
||||
]);
|
||||
|
||||
$permohonanIds = $request->permohonan_ids;
|
||||
$memoNumber = $request->memo_number;
|
||||
$paymentDate = $request->payment_date;
|
||||
$memoDate = $request->memo_date;
|
||||
|
||||
// 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 generate PDF dan menyimpan memo penyelesaian untuk ' . count($permohonanIds) . ' permohonan');
|
||||
|
||||
return redirect()->route('memo.index')
|
||||
->with('success', 'Memo penyelesaian berhasil dibuat dan disimpan untuk ' . count($permohonanIds) . ' permohonan');
|
||||
|
||||
} catch (Exception $e) {
|
||||
DB::rollback();
|
||||
Log::error('MemoController: Error saat generate PDF memo penyelesaian - ' . $e->getMessage());
|
||||
|
||||
return redirect()->back()
|
||||
->withInput()
|
||||
->with('error', 'Terjadi kesalahan saat generate PDF memo penyelesaian: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function untuk mendapatkan informasi jaminan
|
||||
*
|
||||
* @param $permohonanList
|
||||
* @return string
|
||||
*/
|
||||
private function getJaminanInfo($permohonanList)
|
||||
{
|
||||
$jaminanTypes = [];
|
||||
foreach ($permohonanList as $permohonan) {
|
||||
if ($permohonan->tujuanPenilaian) {
|
||||
$jaminanTypes[] = $permohonan->tujuanPenilaian->name;
|
||||
}
|
||||
}
|
||||
|
||||
$uniqueJaminan = array_unique($jaminanTypes);
|
||||
return implode(' & ', $uniqueJaminan);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form action="{{ route('memo.store') }}" method="POST" id="memo-form">
|
||||
<form action="{{ route('memo.preview') }}" method="POST" id="memo-form">
|
||||
@csrf
|
||||
|
||||
<!-- Form Input Memo -->
|
||||
@@ -139,8 +139,8 @@
|
||||
Batal
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary" id="submit-btn">
|
||||
<i class="ki-filled ki-check"></i>
|
||||
Buat Memo Penyelesaian
|
||||
<i class="ki-filled ki-eye"></i>
|
||||
Preview Memo Penyelesaian
|
||||
</button>
|
||||
</div>
|
||||
@else
|
||||
|
||||
298
resources/views/memo/preview.blade.php
Normal file
298
resources/views/memo/preview.blade.php
Normal file
@@ -0,0 +1,298 @@
|
||||
@extends('layouts.main')
|
||||
|
||||
@section('breadcrumbs')
|
||||
{{ Breadcrumbs::render('memo.create') }}
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="grid gap-5 mx-auto w-full lg:gap-7.5">
|
||||
<!-- Preview Memo Penyelesaian -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">
|
||||
Preview Memo Penyelesaian
|
||||
</h3>
|
||||
<div class="flex gap-2 items-center">
|
||||
<button onclick="history.back()" class="btn btn-sm btn-light">
|
||||
<i class="ki-filled ki-black-left"></i>
|
||||
Kembali
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
@if (session('error'))
|
||||
<div class="alert alert-danger">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Template Memo -->
|
||||
<div class="p-8 mx-auto bg-white" style="max-width: 800px; font-family: 'Times New Roman', serif;">
|
||||
<!-- Header Bank -->
|
||||
<div class="flex items-center mb-6">
|
||||
<div class="mr-4">
|
||||
<div class="flex justify-center items-center w-16 h-16 bg-blue-600 rounded">
|
||||
<span class="text-lg font-bold text-white">AGI</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-lg font-bold text-blue-600">BANK</h2>
|
||||
<h2 class="text-lg font-bold text-blue-600">ARTHA GRAHA</h2>
|
||||
<h2 class="text-lg font-bold text-blue-600">INTERNASIONAL</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Judul Memo -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-bold">Memo Instruksi Penyelesaian</h3>
|
||||
<h4 class="text-base font-semibold">Rekening Escrow / KSL Penilai Jaminan</h4>
|
||||
</div>
|
||||
|
||||
<!-- Informasi Memo -->
|
||||
<div class="mb-6 space-y-2">
|
||||
<div class="flex">
|
||||
<span class="w-20 font-medium">Kepada</span>
|
||||
<span class="mr-2">:</span>
|
||||
<span>SUBDIT E-Channel Support & Operation (NOC)</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span class="w-20 font-medium">Dari</span>
|
||||
<span class="mr-2">:</span>
|
||||
<span>SUBDIT Appraisal</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span class="w-20 font-medium">Nomor</span>
|
||||
<span class="mr-2">:</span>
|
||||
<span>{{ $memoData['memo_number'] }}</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span class="w-20 font-medium">Tanggal</span>
|
||||
<span class="mr-2">:</span>
|
||||
<span>{{ \Carbon\Carbon::parse($memoData['memo_date'])->format('d F Y') }}</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span class="w-20 font-medium">Perihal</span>
|
||||
<span class="mr-2">:</span>
|
||||
<span>Penyelesaian Rekening Escrow / <strong>KSL Penilai Jaminan (PJ)</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="mb-6 border-black">
|
||||
|
||||
<!-- Isi Memo -->
|
||||
<div class="mb-6 space-y-4">
|
||||
<p>Sehubungan dengan telah dilakukannya penilaian jaminan dengan keterangan sbb :</p>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div class="flex">
|
||||
<span class="w-32 font-medium">Nama Debitur</span>
|
||||
<span class="mr-2">:</span>
|
||||
<span>Terlampir ({{ $memoData['debitur_count'] }} Debitur)</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span class="w-32 font-medium">Jaminan</span>
|
||||
<span class="mr-2">:</span>
|
||||
<span>{{ $memoData['jaminan_info'] }}</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span class="w-32 font-medium">Total Biaya PJ</span>
|
||||
<span class="mr-2">:</span>
|
||||
<span><strong>Rp
|
||||
{{ number_format($memoData['total_biaya_pj'], 0, ',', '.') }},-</strong></span>
|
||||
</div>
|
||||
<div class="ml-32">
|
||||
<span class="text-sm">({{ terbilang($memoData['total_biaya_pj']) }} Rupiah)</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span class="w-32 font-medium">Pembayaran Tanggal</span>
|
||||
<span class="mr-2">:</span>
|
||||
<span>{{ \Carbon\Carbon::parse($memoData['payment_date'])->format('d F Y') }}</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span class="w-32 font-medium">LPJ selesai dikirim</span>
|
||||
<span class="mr-2">:</span>
|
||||
<span>-</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span class="w-32 font-medium">Jenis Penilaian</span>
|
||||
<span class="mr-2">:</span>
|
||||
<span><strong>KJPP</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mt-4">
|
||||
Kami menginstruksikan kepada Sentra Operasi untuk menyelesaikan Rekening Escrow /
|
||||
<strong>KSL Penilai Jaminan</strong> atas nama debitur tersebut diatas ke <strong>KJPP
|
||||
(terlampir)</strong>.
|
||||
</p>
|
||||
|
||||
<p>Demikian kami sampaikan, atas perhatian dan kerjasamanya kami ucapkan terima kasih.</p>
|
||||
</div>
|
||||
|
||||
<!-- Tanda Tangan -->
|
||||
<div class="flex justify-between mt-12">
|
||||
<div class="text-center">
|
||||
<p class="mb-16">Salam</p>
|
||||
<div class="mx-auto mb-2 w-32 border-b border-black"></div>
|
||||
<p class="font-medium">Innawati Sulina</p>
|
||||
<p class="text-sm">DD Operation 2</p>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<p class="mb-16"> </p>
|
||||
<div class="mx-auto mb-2 w-32 border-b border-black"></div>
|
||||
<p class="font-medium">Wahab N. Wibawa</p>
|
||||
<p class="text-sm">Pgs EO Subdit Appraisal</p>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<p class="mb-16"> </p>
|
||||
<div class="mx-auto mb-2 w-32 border-b border-black"></div>
|
||||
<p class="font-medium">Sumurung P. Siahaan</p>
|
||||
<p class="text-sm"> </p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lampiran -->
|
||||
<div class="mt-8">
|
||||
<p class="mb-2 font-medium">Lampiran :</p>
|
||||
<ul class="space-y-1 text-sm list-disc list-inside">
|
||||
<li>Rekap permohonan penyelesaian biaya KJPP (Eksternal)</li>
|
||||
<li>Asli Invoice & Faktur Pajak KJPP (Eksternal)</li>
|
||||
<li>Copy Tiket Debet (KSL)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Daftar Permohonan -->
|
||||
<div class="mt-8">
|
||||
<h4 class="mb-4 text-lg font-semibold">Daftar Permohonan Terlampir</h4>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-border">
|
||||
<thead>
|
||||
<tr class="bg-gray-50">
|
||||
<th class="text-center">No</th>
|
||||
<th>Nomor Registrasi</th>
|
||||
<th>Debitur</th>
|
||||
<th>Cabang</th>
|
||||
<th>AO</th>
|
||||
<th>Tujuan Penilaian</th>
|
||||
<th class="text-right">Biaya PJ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($permohonanList as $index => $permohonan)
|
||||
<tr>
|
||||
<td class="text-center">{{ $index + 1 }}</td>
|
||||
<td class="font-medium">{{ $permohonan->nomor_registrasi }}</td>
|
||||
<td>{{ $permohonan->debiture->name ?? '-' }}</td>
|
||||
<td>{{ $permohonan->branch->name ?? '-' }}</td>
|
||||
<td>{{ $permohonan->user->name ?? '-' }}</td>
|
||||
<td>
|
||||
@if ($permohonan->tujuanPenilaian)
|
||||
<span class="badge badge-sm badge-primary">
|
||||
{{ $permohonan->tujuanPenilaian->name }}
|
||||
</span>
|
||||
@else
|
||||
-
|
||||
@endif
|
||||
</td>
|
||||
<td class="font-medium text-right">
|
||||
Rp {{ number_format($permohonan->noc->nominal_bayar ?? 0, 0, ',', '.') }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="font-semibold bg-gray-50">
|
||||
<td colspan="6" class="text-right">Total Biaya PJ:</td>
|
||||
<td class="text-right">Rp {{ number_format($totalBiayaPJ, 0, ',', '.') }}</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form untuk Generate PDF -->
|
||||
<form action="{{ route('memo.generate-pdf') }}" method="POST" id="generate-form">
|
||||
@csrf
|
||||
@foreach ($permohonanList as $permohonan)
|
||||
<input type="hidden" name="permohonan_ids[]" value="{{ $permohonan->id }}">
|
||||
@endforeach
|
||||
<input type="hidden" name="memo_number" value="{{ $memoData['memo_number'] }}">
|
||||
<input type="hidden" name="memo_date" value="{{ $memoData['memo_date'] }}">
|
||||
<input type="hidden" name="payment_date" value="{{ $memoData['payment_date'] }}">
|
||||
|
||||
<div class="flex gap-2 justify-end mt-8">
|
||||
<button type="button" onclick="history.back()" class="btn btn-light">
|
||||
<i class="ki-filled ki-black-left"></i>
|
||||
Kembali Edit
|
||||
</button>
|
||||
<button type="button" onclick="window.print()" class="btn btn-secondary">
|
||||
<i class="ki-filled ki-printer"></i>
|
||||
Print Preview
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary" id="generate-btn">
|
||||
<i class="ki-filled ki-file-down"></i>
|
||||
Generate PDF & Simpan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const generateForm = document.getElementById('generate-form');
|
||||
const generateBtn = document.getElementById('generate-btn');
|
||||
|
||||
/**
|
||||
* Event listener untuk form generate PDF
|
||||
*/
|
||||
if (generateForm) {
|
||||
generateForm.addEventListener('submit', function(e) {
|
||||
// Konfirmasi sebelum generate
|
||||
const confirmed = confirm(
|
||||
'Apakah Anda yakin ingin generate PDF dan menyimpan memo penyelesaian ini?'
|
||||
);
|
||||
if (!confirmed) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disable submit button untuk mencegah double submit
|
||||
if (generateBtn) {
|
||||
generateBtn.disabled = true;
|
||||
generateBtn.innerHTML = '<i class="ki-filled ki-loading"></i> Memproses...';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Print styles
|
||||
const printStyles = `
|
||||
<style>
|
||||
@media print {
|
||||
body * {
|
||||
visibility: hidden;
|
||||
}
|
||||
.card-body, .card-body * {
|
||||
visibility: visible;
|
||||
}
|
||||
.card-body {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.btn, .card-header, .mt-8:last-child {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
document.head.insertAdjacentHTML('beforeend', printStyles);
|
||||
</script>
|
||||
@endpush
|
||||
@@ -801,6 +801,11 @@ Breadcrumbs::for('memo.create', function (BreadcrumbTrail $trail) {
|
||||
$trail->push('Create', route('memo.index'));
|
||||
});
|
||||
|
||||
Breadcrumbs::for('memo.preview', function (BreadcrumbTrail $trail) {
|
||||
$trail->parent('memo.create');
|
||||
$trail->push('Preview', route('memo.index'));
|
||||
});
|
||||
|
||||
|
||||
|
||||
// add andy
|
||||
|
||||
@@ -402,6 +402,8 @@ Route::middleware(['auth'])->group(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::post('preview', [MemoController::class, 'preview'])->name('memo.preview');
|
||||
Route::post('generate-pdf', [MemoController::class, 'generatePdf'])->name('memo.generate-pdf');
|
||||
});
|
||||
Route::resource('memo', MemoController::class);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user