feat(noc): tambah fitur penyelesaian dan memo penyelesaian NOC

- Menambahkan relasi `noc` pada data laporan.
- Memperbarui logika `update` NOC untuk memproses memo penyelesaian.
- Menambahkan tombol "Penyelesaian" pada halaman laporan jika data NOC belum selesai.
- Memperbaiki pengecekan keberadaan file memo dengan disk publik.
- Mengimplementasikan view dan form baru untuk input memo penyelesaian pada NOC.
- Menambahkan routing dan breadcrumbs untuk proses penyelesaian memo NOC.
- Menambahkan validasi dan penyimpanan file memo penyelesaian.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
This commit is contained in:
Daeng Deni Mardaeni
2025-05-07 10:29:02 +07:00
parent 8158f3058f
commit 980b4e8e9f
7 changed files with 211 additions and 6 deletions

View File

@@ -123,7 +123,7 @@ class LaporanController extends Controller
$filteredRecords = $query->count(); $filteredRecords = $query->count();
// Get the data for the current page // Get the data for the current page
$data = $query->with(['user', 'debiture', 'branch', 'tujuanPenilaian', 'penilaian','jenisFasilitasKredit', 'documents.inspeksi','penilai','documents.detail'])->get(); $data = $query->with(['user', 'debiture', 'branch', 'tujuanPenilaian', 'penilaian','jenisFasilitasKredit', 'documents.inspeksi','penilai','documents.detail','noc'])->get();
// Calculate the page count // Calculate the page count
$pageCount = ceil($totalRecords / $size); $pageCount = ceil($totalRecords / $size);

View File

@@ -100,6 +100,27 @@
public function update(NocRequest $request, PersetujuanPenawaran $persetujuanPenawaran) public function update(NocRequest $request, PersetujuanPenawaran $persetujuanPenawaran)
{ {
$validated = $request->validated(); $validated = $request->validated();
if($request->get('is_memo')){
$memo = Noc::find($request->get('is_memo'));
$folderPath = 'noc/' . request()->get('persetujuan_penawaran_id') . '/memo_penyelesaian/';
if ($request->hasFile('memo_penyelesaian')) {
$memo->memo_penyelesaian = $request->file('memo_penyelesaian')->store(
$folderPath,
'public',
);
}
$memo->catatan_noc = $validated['catatan_noc'];
$memo->save();
return redirect()
->route('laporan.index')->with('success', 'Memo Penyelesaian updated successfully');
}
$dataNoc = [ $dataNoc = [
'nominal_penyelesaian' => $validated['nominal_penyelesaian'], 'nominal_penyelesaian' => $validated['nominal_penyelesaian'],
'tanggal_penyelesaian' => $validated['tanggal_penyelesaian'] ?? date('Y-m-d'), 'tanggal_penyelesaian' => $validated['tanggal_penyelesaian'] ?? date('Y-m-d'),
@@ -142,7 +163,11 @@
/** /**
* Display the specified resource. * Display the specified resource.
*/ */
public function show($id) {} public function show(Request $request) {
$noc = Noc::find($request->get('id'));
return view('lpj::noc.memo', compact('noc'));
}
/** /**
* Show the form for editing the specified resource. * Show the form for editing the specified resource.

View File

@@ -237,8 +237,17 @@
const type = data.penilai?.type || ''; const type = data.penilai?.type || '';
let laporanButton = ''; let laporanButton = '';
let resumeButton = ''; let resumeButton = '';
let penyelesaian = '';
if(data.noc) {
if (!data.noc?.tanggal_penyelesaian && !data.noc?.memo_penyelesaian) {
penyelesaian = `
<a href="{{ route('noc.penyelesaian') }}?id=${data.noc.id}&permohonanId=${data.noc.permohonan_id}&persetujuanPenawaranId=${data.noc.persetujuan_penawaran_id}" class="btn btn-sm btn-warning">
Penyelesaian
</a>`;
}
}
if(data.penilai.resume) { if(data.penilai.resume) {
resumeButton = ` resumeButton = `
@@ -270,7 +279,7 @@
} }
return `<div class="flex flex-wrap justify-end gap-1.5"> ${resumeButton} ${laporanButton} </div>`; return `<div class="flex flex-wrap justify-end gap-1.5"> ${penyelesaian} ${resumeButton} ${laporanButton} </div>`;
}, },
} }
}, },

View File

@@ -8,7 +8,7 @@
$hasMemo = false; $hasMemo = false;
try { try {
$memoPath = $persetujuanPenawaran->noc->memo_penyelesaian ?? null; $memoPath = $persetujuanPenawaran->noc->memo_penyelesaian ?? null;
$hasMemo = isset($memoPath) && !empty($memoPath) && Storage::exists($memoPath); $hasMemo = isset($memoPath) && !empty($memoPath) && Storage::disk('public')->exists($memoPath);
} catch (Exception $e) { } catch (Exception $e) {
// Jika terjadi error, $hasMemo tetap false // Jika terjadi error, $hasMemo tetap false
} }
@@ -19,7 +19,7 @@
<div class="card border border-agi-100 pb-2.5"> <div class="card border border-agi-100 pb-2.5">
<div class="card-header bg-agi-50" id="basic_settings"> <div class="card-header bg-agi-50" id="basic_settings">
<div class="card-title flex flex-row gap-1.5"> <div class="card-title flex flex-row gap-1.5">
NOC {{ $hasMemo ? 'Proses Penyelesaian NOC' : 'NOC' }}
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<a href="{{ route('noc.index') }}" class="btn btn-xs btn-info"><i class="ki-filled ki-exit-left"></i> Back</a> <a href="{{ route('noc.index') }}" class="btn btn-xs btn-info"><i class="ki-filled ki-exit-left"></i> Back</a>
@@ -28,7 +28,7 @@
<div class="card-body"> <div class="card-body">
<form action="{{ !$hasMemo ? route('noc.store') : route('noc.update',$persetujuanPenawaran) }}" method="POST" class="grid gap-5" enctype="multipart/form-data"> <form action="{{ !$hasMemo ? route('noc.store') : route('noc.update',$persetujuanPenawaran) }}" method="POST" class="grid gap-5" enctype="multipart/form-data">
@csrf @csrf
@if(!$hasMemo) @if($hasMemo)
@method('PUT') @method('PUT')
@endif @endif
<input type="hidden" name="penawaran_id" value="{{ $persetujuanPenawaran->penawaran_id ?? old('penawaran_id') }}"> <input type="hidden" name="penawaran_id" value="{{ $persetujuanPenawaran->penawaran_id ?? old('penawaran_id') }}">

View File

@@ -0,0 +1,163 @@
@extends('layouts.main')
@section('breadcrumbs')
{{ Breadcrumbs::render(request()->route()->getName()) }}
@endsection
@section('content')
<div class="w-full grid gap-5 lg:gap-7.5 mx-auto">
<div class="card border border-agi-100 pb-2.5">
<div class="card-header bg-agi-50" id="basic_settings">
<div class="card-title flex flex-row gap-1.5">
Memo Penyelesaian NOC
</div>
<div class="flex items-center gap-2">
<a href="{{ route('noc.index') }}" class="btn btn-xs btn-info"><i class="ki-filled ki-exit-left"></i> Back</a>
</div>
</div>
<div class="card-body">
<form action="{{ route('noc.update',$noc) }}" method="POST" class="grid gap-5" enctype="multipart/form-data">
@csrf
@method('PUT')
<input type="hidden" name="persetujuan_penawaran_id" value="{{ $noc->persetujuan_penawaran_id }}">
<input type="hidden" name="permohonan_id" value="{{ $noc->permohonan_id }}">
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Status Bayar
</label>
<div class="flex flex-wrap items-baseline w-full">
<select class="input tomselect w-full @error('status_pembayar') border-danger bg-danger-light @enderror" name="status_pembayar" id="status_pembayar" disabled>
<option value="">Pilih Status Bayar</option>
<option value="sudah_bayar" {{ (old('status_pembayar') == 'sudah_bayar') || ($noc?->permohonan?->status_bayar == 'sudah_bayar') ? 'selected' : '' }}>Sudah Bayar</option>
<option value="belum_bayar" {{ (old('status_pembayar') == 'belum_bayar') || ($noc?->permohonan?->status_bayar == 'belum_bayar') ? 'selected' : '' }}>Belum Bayar</option>
</select>
@error('status_bayar')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Bukti Pembayaran
</label>
<div class="flex flex-wrap items-baseline w-full">
@if(!empty($noc->persetujuanPenawaran->bukti_bayar))
<div class="mt-2 flex items-center">
<a href="{{ Storage::url($noc->persetujuanPenawaran->bukti_bayar) }}" target="_blank" class="badge badge-sm badge-outline badge-warning">
<i class="ki-filled ki-eye mr-2"></i> Lihat File
</a>
</div>
@endif
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Nominal Bayar
</label>
<div class="flex flex-wrap items-baseline w-full">
<input type="number" name="total_harus_bayar" id="total_harus_bayar" class="input w-full @error('total_harus_bayar') border-danger bg-danger-light @enderror" value="{{ old('total_harus_bayar', $noc->nominal_bayar ?? '') }}" readonly>
@error('total_harus_bayar')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Nominal Diterima
</label>
<div class="flex flex-wrap items-baseline w-full">
<input type="number" name="nominal_bayar" id="nominal_bayar" class="input w-full @error('nominal_bayar') border-danger bg-danger-light @enderror" value="{{ old('nominal_bayar', $noc->nominal_bayar ?? '') }}" placeholder="Masukkan nominal bayar" readonly>
@error('nominal_bayar')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Tanggal Pembayaran
</label>
<div class="flex flex-wrap items-baseline w-full">
<input type="date" name="tanggal_pembayaran" id="tanggal_pembayaran" class="input w-full @error('tanggal_pembayaran') border-danger bg-danger-light @enderror" value="{{ old('tanggal_pembayaran', isset($noc->tanggal_pembayaran) ? date('Y-m-d', strtotime($noc->tanggal_pembayaran)) : '') }}" readonly>
@error('tanggal_pembayaran')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Bukti KSL
</label>
<div class="flex flex-wrap items-baseline w-full">
@if(isset($noc->bukti_ksl) && !empty($noc->bukti_ksl))
<div class="mt-2 flex items-center">
<a href="{{ Storage::url($noc->bukti_ksl) }}" target="_blank" class="badge badge-sm badge-outline badge-warning">
<i class="ki-filled ki-eye mr-2"></i> Lihat File
</a>
</div>
@endif
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Memo Penyelesaian
</label>
<div class="flex flex-wrap items-baseline w-full">
<input type="hidden" name="is_memo" value="{{ $noc->id }}">
<input type="file" name="memo_penyelesaian" id="memo_penyelesaian" class="file-input w-full @error('memo_penyelesaian') border-danger bg-danger-light @enderror" accept=".pdf,.jpg,.jpeg,.png">
@error('memo_penyelesaian')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
@if(isset($noc->memo_penyelesaian) && !empty($noc->memo_penyelesaian))
<div class="mt-2 flex items-center">
<a href="{{ Storage::url($noc->memo_penyelesaian) }}" target="_blank" class="badge badge-sm badge-outline badge-warning">
<i class="ki-filled ki-eye mr-2"></i> Lihat File
</a>
</div>
@endif
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Catatan
</label>
<div class="flex flex-wrap items-baseline w-full">
<textarea name="catatan" id="catatan" rows="4" class="textarea w-full @error('catatan') border-danger bg-danger-light @enderror" readonly placeholder="Masukkan catatan">{{ old('catatan', $noc->catatan ?? '') }}</textarea>
@error('catatan')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Catatan NOC
</label>
<div class="flex flex-wrap items-baseline w-full">
<textarea name="catatan_noc" id="catatan_noc" rows="4" class="textarea w-full @error('catatan_noc') border-danger bg-danger-light @enderror" placeholder="Masukkan catatan noc">{{ old('catatan_noc', $noc->catatan_noc ?? '') }}</textarea>
@error('catatan_noc')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex justify-end">
<button type="submit" class="btn btn-primary">
Proses
</button>
</div>
</form>
</div>
</div>
</div>
@endsection

View File

@@ -678,6 +678,11 @@ Breadcrumbs::for('noc.edit', function (BreadcrumbTrail $trail) {
$trail->push('Proses NOC'); $trail->push('Proses NOC');
}); });
Breadcrumbs::for('noc.penyelesaian', function (BreadcrumbTrail $trail) {
$trail->parent('noc');
$trail->push('Proses Memo Penyelesaian NOC');
});
Breadcrumbs::for('laporan-external', function (BreadcrumbTrail $trail) { Breadcrumbs::for('laporan-external', function (BreadcrumbTrail $trail) {
$trail->push('Laporan External', route('laporan-external.index')); $trail->push('Laporan External', route('laporan-external.index'));
}); });

View File

@@ -654,6 +654,9 @@ Route::middleware(['auth'])->group(function () {
Route::get('noc/datatables', [NocController::class, 'dataForDatatables']) Route::get('noc/datatables', [NocController::class, 'dataForDatatables'])
->name('noc.datatables'); ->name('noc.datatables');
Route::get('noc/penyelesaian',[NocController::class, 'show'])->name('noc.penyelesaian');
Route::post('noc/penyelesaian',[NocController::class, 'penyelesaian'])->name('noc.store.penyelesaian');
Route::resource('noc', NocController::class); Route::resource('noc', NocController::class);