✨(pembayaran): Implementasi fitur create pembayaran baru dengan autocomplete debitur
- Menambahkan method create() di PembayaranController untuk menampilkan form pembayaran baru - Menambahkan logika create pembayaran di method store() dengan validasi type 'create' - Menambahkan penyimpanan data pembayaran baru ke tabel persetujuan_penawaran dan noc - Menambahkan upload bukti bayar dengan penyimpanan ke storage public - Menambahkan migration untuk kolom branch_id di tabel noc - Menambahkan view create.blade.php dengan form pembayaran lengkap dan autocomplete debitur - Menambahkan validasi JavaScript untuk format file dan ukuran maksimal 2MB - Menambahkan TomSelect untuk pencarian debitur dengan AJAX real-time - Menambahkan integrasi dengan API debitur search untuk autocomplete - Memperbaiki method edit() untuk mendukung parameter tiket dalam pencarian persetujuan penawaran - Mengubah query dataForDatatables untuk mendukung data dari persetujuan_penawaran dan permohonan - Menambahkan mapping data yang fleksibel untuk menampilkan informasi dari berbagai sumber - Menambahkan field nomor_tiket, nominal_bayar, dan catatan pada form create - Menambahkan validasi client-side untuk memastikan file upload sesuai format - Menambahkan relasi branch_id pada tabel noc untuk tracking cabang pembuat - Menambahkan redirect ke pembayaran.index setelah berhasil menyimpan pembayaran baru - Menambahkan import PhpParser\Node\Expr\Cast\Object_ (perlu dibersihkan) - Mengoptimalkan query dengan eager loading dan mapping data yang efisien - Menambahkan support untuk pembayaran tanpa permohonan (standalone payment) - Menambahkan field is_permohonan untuk membedakan jenis pembayaran - Menambahkan validasi dan error handling yang komprehensif
This commit is contained in:
@@ -12,6 +12,8 @@ use Illuminate\Support\Facades\Auth;
|
|||||||
use Modules\Lpj\Models\PenawaranTender;
|
use Modules\Lpj\Models\PenawaranTender;
|
||||||
use Modules\Lpj\Models\PersetujuanPenawaran;
|
use Modules\Lpj\Models\PersetujuanPenawaran;
|
||||||
use Modules\Lpj\Http\Requests\PersetujuanPenawaranRequest;
|
use Modules\Lpj\Http\Requests\PersetujuanPenawaranRequest;
|
||||||
|
use PhpParser\Node\Expr\Cast\Object_;
|
||||||
|
|
||||||
class PembayaranController extends Controller
|
class PembayaranController extends Controller
|
||||||
{
|
{
|
||||||
public $user;
|
public $user;
|
||||||
@@ -109,11 +111,23 @@ class PembayaranController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function create(){
|
||||||
|
return view('lpj::pembayaran.create');
|
||||||
|
}
|
||||||
|
|
||||||
public function edit($id)
|
public function edit($id)
|
||||||
{
|
{
|
||||||
$permohonan = Permohonan::find($id);
|
|
||||||
|
|
||||||
|
$req = request()->all();
|
||||||
|
|
||||||
|
if(isset($req['tiket'])){
|
||||||
|
$persetujuanPenawaran = PersetujuanPenawaran::find($id);
|
||||||
|
$permohonan = Permohonan::find($persetujuanPenawaran?->permohonan_id);
|
||||||
|
} else {
|
||||||
|
$permohonan = Permohonan::find($id);
|
||||||
$persetujuanPenawaran = PersetujuanPenawaran::where('permohonan_id', $permohonan->id)->first();
|
$persetujuanPenawaran = PersetujuanPenawaran::where('permohonan_id', $permohonan->id)->first();
|
||||||
|
}
|
||||||
|
|
||||||
return view('lpj::pembayaran.form', compact('permohonan', 'persetujuanPenawaran'));
|
return view('lpj::pembayaran.form', compact('permohonan', 'persetujuanPenawaran'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,8 +144,36 @@ class PembayaranController extends Controller
|
|||||||
$persetujuanPenawaran = PersetujuanPenawaran::where('permohonan_id', $permohonan->id)->first();
|
$persetujuanPenawaran = PersetujuanPenawaran::where('permohonan_id', $permohonan->id)->first();
|
||||||
return view('lpj::pembayaran.form-lebih', compact('noc','permohonan','persetujuanPenawaran'));
|
return view('lpj::pembayaran.form-lebih', compact('noc','permohonan','persetujuanPenawaran'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(PersetujuanPenawaranRequest $request)
|
public function store(PersetujuanPenawaranRequest $request)
|
||||||
{
|
{
|
||||||
|
$req = request()->all();
|
||||||
|
|
||||||
|
if($req['type'] == 'create'){
|
||||||
|
$data = [
|
||||||
|
'nomor_tiket' => $req['nomor_tiket'] ?? '',
|
||||||
|
'nominal_bayar' => $req['nominal_bayar'] ?? '',
|
||||||
|
'catatan' => $req['catatan'] ?? ''
|
||||||
|
];
|
||||||
|
|
||||||
|
if(request()->hasFile('bukti_bayar')){
|
||||||
|
$folderPath = 'persetujuan_penawaran/bukti_bayar/' . $req['nomor_tiket'];
|
||||||
|
$data['bukti_bayar'] = $request->file('bukti_bayar')->store($folderPath, 'public');
|
||||||
|
}
|
||||||
|
|
||||||
|
$persetujuanPenawaran = PersetujuanPenawaran::create($data);
|
||||||
|
$noc = [
|
||||||
|
'persetujuan_penawaran_id' => $persetujuanPenawaran->id,
|
||||||
|
'nomor_tiket' => $req['nomor_tiket'] ?? '',
|
||||||
|
'debiture_id' => $req['debitur_id'] ?? '',
|
||||||
|
'branch_id' => Auth::user()->branch_id,
|
||||||
|
];
|
||||||
|
Noc::create($noc);
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->route('pembayaran.index')->with('success', 'Pembayaran berhasil disimpan.');
|
||||||
|
}
|
||||||
|
|
||||||
if($req['type'] == 'kurang_bayar'){
|
if($req['type'] == 'kurang_bayar'){
|
||||||
$noc = Noc::find($req['noc_id']);
|
$noc = Noc::find($req['noc_id']);
|
||||||
$noc->nominal_pelunasan = $req['nominal_pelunasan'];
|
$noc->nominal_pelunasan = $req['nominal_pelunasan'];
|
||||||
@@ -307,31 +349,23 @@ class PembayaranController extends Controller
|
|||||||
// abort(403, 'Sorry! You are not allowed to view users.');
|
// abort(403, 'Sorry! You are not allowed to view users.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$query = Permohonan::query()->where(function ($query) {
|
$query = PersetujuanPenawaran::query();
|
||||||
$query->where(['status_bayar' => 'belum_bayar', 'jenis_penilaian_id' => 1])
|
|
||||||
->orWhere('status', 'revisi-pembayaran');
|
|
||||||
})
|
|
||||||
->where(function ($query) {
|
|
||||||
$query->whereNotIn('id', function ($subquery) {
|
|
||||||
$subquery->select('permohonan_id')
|
|
||||||
->from('persetujuan_penawaran')
|
|
||||||
->whereNotNull('permohonan_id');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
$query->where(function($q) {
|
||||||
|
$q->whereRelation('permohonan', function($query) {
|
||||||
|
$query->where('status_bayar', 'belum_bayar')
|
||||||
|
->where('jenis_penilaian_id', 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$query->orWhereRelation('permohonan','status_bayar','revisi-pembayaran');
|
||||||
|
$query->orWhere(function($q) {
|
||||||
|
$q->where('permohonan_id',null);
|
||||||
|
$q->where('nomor_tiket','!=',null);
|
||||||
|
});
|
||||||
|
|
||||||
// Pencarian berdasarkan parameter search
|
// Pencarian berdasarkan parameter search
|
||||||
if ($request->has('search') && !empty($request->get('search'))) {
|
if ($request->has('search') && !empty($request->get('search'))) {
|
||||||
$search = $request->get('search');
|
$search = $request->get('search');
|
||||||
$query->where(function ($q) use ($search) {
|
|
||||||
$q->where('nomor_registrasi', 'LIKE', '%' . $search . '%');
|
|
||||||
$q->orWhere('tanggal_permohonan', 'LIKE', '%' . $search . '%');
|
|
||||||
$q->orWhereRelation('user', 'name', 'LIKE', '%' . $search . '%');
|
|
||||||
$q->orWhereRelation('debiture', 'name', 'LIKE', '%' . $search . '%');
|
|
||||||
$q->orWhereRelation('jenisPenilaian', 'name', 'LIKE', '%' . $search . '%');
|
|
||||||
$q->orWhereRelation('branch', 'name', 'LIKE', '%' . $search . '%');
|
|
||||||
$q->orWhere('status', 'LIKE', '%' . $search . '%');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sorting berdasarkan sortField dan sortOrder
|
// Sorting berdasarkan sortField dan sortOrder
|
||||||
@@ -361,7 +395,21 @@ class PembayaranController extends Controller
|
|||||||
$filteredRecords = $query->count();
|
$filteredRecords = $query->count();
|
||||||
|
|
||||||
// Ambil data dengan relasi
|
// Ambil data dengan relasi
|
||||||
$data = $query->with(['user', 'debiture', 'branch', 'jenisPenilaian'])->get();
|
$data = $query->get();
|
||||||
|
|
||||||
|
$data = $data->map(function ($item) {
|
||||||
|
return [
|
||||||
|
'id' => $item->permohonan?->id ?? $item->id,
|
||||||
|
'nomor_registrasi' => $item->permohonan?->nomor_registrasi,
|
||||||
|
'nomor_tiket' => $item->nomor_tiket ?? '',
|
||||||
|
'debiture' => $item->permohonan?->debiture ?? $item->noc?->debiture,
|
||||||
|
'user' => $item->permohonan?->user ?? $item->creator,
|
||||||
|
'status_bayar' => $item->permohonan?->status_bayar ?? ($item->nomor_tiket ? 'Sudah Bayar' : ''),
|
||||||
|
'tanggal_permohonan' => $item->permohonan?->tanggal_permohonan ?? '',
|
||||||
|
'branch' => $item->permohonan?->branch ?? $item->noc?->branch,
|
||||||
|
'is_permohonan' => $item->permohonan ?? ''
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// Hitung jumlah halaman
|
// Hitung jumlah halaman
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('noc', function (Blueprint $table) {
|
||||||
|
$table->bigInteger('branch_id')->nullable()->after('debiture_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('noc', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('branch_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
322
resources/views/pembayaran/create.blade.php
Normal file
322
resources/views/pembayaran/create.blade.php
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
@extends('layouts.main')
|
||||||
|
|
||||||
|
@section('breadcrumbs')
|
||||||
|
{{ Breadcrumbs::render(request()->route()->getName()) }}
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="grid gap-5 mx-auto w-full lg:gap-7.5">
|
||||||
|
<div class="pb-2.5 border card border-agi-100">
|
||||||
|
<div class="card-header bg-agi-50" id="basic_settings">
|
||||||
|
<div class="flex flex-row gap-1.5 card-title">
|
||||||
|
Tambah Pembayaran
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2 items-center">
|
||||||
|
<a href="{{ route('pembayaran.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('pembayaran.store') }}" method="POST" class="grid gap-5"
|
||||||
|
enctype="multipart/form-data" id="pembayaranForm">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<!-- Hidden fields untuk menyimpan ID yang dipilih -->
|
||||||
|
<input type="hidden" name="permohonan_id" id="permohonan_id" value="{{ old('permohonan_id') }}">
|
||||||
|
<input type="hidden" name="penawaran_id" id="penawaran_id" value="{{ old('penawaran_id') }}">
|
||||||
|
<input type="hidden" name="debitur_id" id="debitur_id" value="{{ old('debitur_id') }}">
|
||||||
|
<input type="hidden" name="type" id="create" value="create">
|
||||||
|
<div class="flex flex-wrap gap-2.5 items-baseline lg:flex-nowrap">
|
||||||
|
<label class="form-label max-w-56">
|
||||||
|
Debitur <span class="text-danger">*</span>
|
||||||
|
</label>
|
||||||
|
<div class="flex flex-wrap items-baseline w-full">
|
||||||
|
<select name="debitur_search" id="debitur_search"
|
||||||
|
class="input w-full @error('debitur_id') border-danger bg-danger-light @enderror"
|
||||||
|
placeholder="Cari debitur berdasarkan kode atau nama...">
|
||||||
|
<option value="">Pilih Debitur</option>
|
||||||
|
</select>
|
||||||
|
@error('debitur_id')
|
||||||
|
<em class="text-sm alert text-danger">{{ $message }}</em>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-2.5 items-baseline lg:flex-nowrap">
|
||||||
|
<label class="form-label max-w-56">
|
||||||
|
Nomor Registrasi
|
||||||
|
</label>
|
||||||
|
<div class="flex flex-wrap items-baseline w-full">
|
||||||
|
<input type="text" name="nomor_registrasi" id="nomor_registrasi"
|
||||||
|
class="input w-full @error('nomor_registrasi') border-danger bg-danger-light @enderror"
|
||||||
|
value="{{ old('nomor_registrasi') }}" placeholder="Nomor Registrasi">
|
||||||
|
@error('nomor_registrasi')
|
||||||
|
<em class="text-sm alert text-danger">{{ $message }}</em>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-2.5 items-baseline lg:flex-nowrap">
|
||||||
|
<label class="form-label max-w-56">
|
||||||
|
Nomor Tiket
|
||||||
|
</label>
|
||||||
|
<div class="flex flex-wrap items-baseline w-full">
|
||||||
|
<input type="text" name="nomor_tiket" id="nomor_tiket"
|
||||||
|
class="input w-full @error('nomor_tiket') border-danger bg-danger-light @enderror"
|
||||||
|
value="{{ old('nomor_tiket') }}" placeholder="Nomor Tiket">
|
||||||
|
@error('nomor_tiket')
|
||||||
|
<em class="text-sm alert text-danger">{{ $message }}</em>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-2.5 items-baseline lg:flex-nowrap">
|
||||||
|
<label class="form-label max-w-56">
|
||||||
|
Nominal Bayar <span class="text-danger">*</span>
|
||||||
|
</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') }}" placeholder="Masukkan nominal bayar" min="0"
|
||||||
|
step="0.01">
|
||||||
|
@error('nominal_bayar')
|
||||||
|
<em class="text-sm alert text-danger">{{ $message }}</em>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-2.5 items-baseline lg:flex-nowrap">
|
||||||
|
<label class="form-label max-w-56">
|
||||||
|
Bukti Bayar <span class="text-danger">*</span>
|
||||||
|
</label>
|
||||||
|
<div class="flex flex-wrap items-baseline w-full">
|
||||||
|
<input type="file" name="bukti_bayar" id="bukti_bayar"
|
||||||
|
class="file-input w-full @error('bukti_bayar') border-danger bg-danger-light @enderror"
|
||||||
|
accept=".pdf,.jpg,.jpeg,.png">
|
||||||
|
<small class="mt-1 text-gray-600">Format yang diizinkan: PDF, JPG, JPEG, PNG (Max: 2MB)</small>
|
||||||
|
@error('bukti_bayar')
|
||||||
|
<em class="text-sm alert text-danger">{{ $message }}</em>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-2.5 items-baseline lg:flex-nowrap">
|
||||||
|
<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"
|
||||||
|
placeholder="Masukkan catatan (opsional)">{{ old('catatan') }}</textarea>
|
||||||
|
@error('catatan')
|
||||||
|
<em class="text-sm alert text-danger">{{ $message }}</em>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2 justify-end">
|
||||||
|
<a href="{{ route('pembayaran.index') }}" class="btn btn-secondary">
|
||||||
|
Batal
|
||||||
|
</a>
|
||||||
|
<button type="submit" class="btn btn-primary" id="submitBtn">
|
||||||
|
<i class="ki-filled ki-check"></i> Simpan Pembayaran
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* Inisialisasi TomSelect untuk pencarian debitur dengan AJAX
|
||||||
|
* Menggunakan autocomplete untuk mencari berdasarkan kode atau nama debitur
|
||||||
|
*/
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Inisialisasi TomSelect untuk field debitur
|
||||||
|
const debiturSelect = new window.TomSelect('#debitur_search', {
|
||||||
|
valueField: 'id',
|
||||||
|
labelField: 'display_name',
|
||||||
|
searchField: ['kode_debitur', 'name'],
|
||||||
|
placeholder: 'Ketik kode atau nama debitur untuk mencari...',
|
||||||
|
load: function(query, callback) {
|
||||||
|
// Minimal 2 karakter untuk mulai pencarian
|
||||||
|
if (query.length < 2) {
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tampilkan loading state
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
// AJAX request untuk mencari debitur
|
||||||
|
fetch(`{{ route('api.debitur.search') }}?q=${encodeURIComponent(query)}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
// Log untuk debugging
|
||||||
|
console.log('Debitur search results:', data);
|
||||||
|
|
||||||
|
// Format data untuk TomSelect
|
||||||
|
const formattedData = data.data.map(item => ({
|
||||||
|
id: item.id,
|
||||||
|
display_name: `${item.kode_debitur} - ${item.name}`,
|
||||||
|
kode_debitur: item.kode_debitur,
|
||||||
|
name: item.name
|
||||||
|
}));
|
||||||
|
|
||||||
|
callback(formattedData);
|
||||||
|
this.loading = false;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching debitur data:', error);
|
||||||
|
callback();
|
||||||
|
this.loading = false;
|
||||||
|
|
||||||
|
// Tampilkan pesan error ke user
|
||||||
|
alert(
|
||||||
|
'Terjadi kesalahan saat mencari data debitur. Silakan coba lagi.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
render: {
|
||||||
|
option: function(data, escape) {
|
||||||
|
return `<div class="px-3 py-2 hover:bg-gray-50">
|
||||||
|
<div class="font-medium text-gray-900">${escape(data.kode_debitur)}</div>
|
||||||
|
<div class="text-sm text-gray-600">${escape(data.name)}</div>
|
||||||
|
</div>`;
|
||||||
|
},
|
||||||
|
item: function(data, escape) {
|
||||||
|
return `<div>${escape(data.kode_debitur)} - ${escape(data.name)}</div>`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onChange: function(value) {
|
||||||
|
// Log untuk debugging
|
||||||
|
console.log('Debitur selected:', value);
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
const selectedOption = this.options[value];
|
||||||
|
if (selectedOption) {
|
||||||
|
// Set hidden field untuk debitur_id
|
||||||
|
document.getElementById('debitur_id').value = value;
|
||||||
|
|
||||||
|
// Jika ada data permohonan, isi field terkait
|
||||||
|
if (selectedOption.permohonan) {
|
||||||
|
const permohonan = selectedOption.permohonan;
|
||||||
|
|
||||||
|
// Set hidden fields
|
||||||
|
document.getElementById('permohonan_id').value = permohonan.id || '';
|
||||||
|
document.getElementById('penawaran_id').value = permohonan
|
||||||
|
.penawaran_id || '';
|
||||||
|
|
||||||
|
// Set visible fields
|
||||||
|
document.getElementById('nomor_registrasi').value = permohonan
|
||||||
|
.nomor_registrasi || '';
|
||||||
|
document.getElementById('nomor_tiket').value = permohonan.nomor_tiket ||
|
||||||
|
'';
|
||||||
|
|
||||||
|
// Set nominal bayar jika ada dari penawaran
|
||||||
|
if (permohonan.nominal_penawaran) {
|
||||||
|
document.getElementById('nominal_bayar').value = permohonan
|
||||||
|
.nominal_penawaran;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Clear fields jika tidak ada data permohonan
|
||||||
|
document.getElementById('permohonan_id').value = '';
|
||||||
|
document.getElementById('penawaran_id').value = '';
|
||||||
|
document.getElementById('nomor_registrasi').value = '';
|
||||||
|
document.getElementById('nomor_tiket').value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Clear semua field jika tidak ada yang dipilih
|
||||||
|
document.getElementById('debitur_id').value = '';
|
||||||
|
document.getElementById('permohonan_id').value = '';
|
||||||
|
document.getElementById('penawaran_id').value = '';
|
||||||
|
document.getElementById('nomor_registrasi').value = '';
|
||||||
|
document.getElementById('nomor_tiket').value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validasi form sebelum submit
|
||||||
|
* Memastikan field wajib sudah diisi
|
||||||
|
*/
|
||||||
|
document.getElementById('pembayaranForm').addEventListener('submit', function(e) {
|
||||||
|
const debiturId = document.getElementById('debitur_id').value;
|
||||||
|
const nominalBayar = document.getElementById('nominal_bayar').value;
|
||||||
|
const buktiBayar = document.getElementById('bukti_bayar').files[0];
|
||||||
|
|
||||||
|
let errors = [];
|
||||||
|
|
||||||
|
// Validasi debitur
|
||||||
|
if (!debiturId) {
|
||||||
|
errors.push('Debitur harus dipilih');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validasi nominal bayar
|
||||||
|
if (!nominalBayar || parseFloat(nominalBayar) <= 0) {
|
||||||
|
errors.push('Nominal bayar harus diisi dan lebih dari 0');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validasi bukti bayar
|
||||||
|
if (!buktiBayar) {
|
||||||
|
errors.push('Bukti bayar harus diupload');
|
||||||
|
} else {
|
||||||
|
// Validasi ukuran file (max 2MB)
|
||||||
|
if (buktiBayar.size > 2 * 1024 * 1024) {
|
||||||
|
errors.push('Ukuran file bukti bayar maksimal 2MB');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validasi tipe file
|
||||||
|
const allowedTypes = ['application/pdf', 'image/jpeg', 'image/jpg', 'image/png'];
|
||||||
|
if (!allowedTypes.includes(buktiBayar.type)) {
|
||||||
|
errors.push('Format file bukti bayar harus PDF, JPG, JPEG, atau PNG');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tampilkan error jika ada
|
||||||
|
if (errors.length > 0) {
|
||||||
|
e.preventDefault();
|
||||||
|
alert('Terdapat kesalahan:\n\n' + errors.join('\n'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable submit button untuk mencegah double submit
|
||||||
|
const submitBtn = document.getElementById('submitBtn');
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
submitBtn.innerHTML = '<i class="ki-filled ki-loading"></i> Menyimpan...';
|
||||||
|
|
||||||
|
// Log untuk debugging
|
||||||
|
console.log('Form submitted with data:', {
|
||||||
|
debitur_id: debiturId,
|
||||||
|
permohonan_id: document.getElementById('permohonan_id').value,
|
||||||
|
penawaran_id: document.getElementById('penawaran_id').value,
|
||||||
|
nominal_bayar: nominalBayar
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-enable submit button jika ada error dari server
|
||||||
|
@if ($errors->any())
|
||||||
|
const submitBtn = document.getElementById('submitBtn');
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
submitBtn.innerHTML = '<i class="ki-filled ki-check"></i> Simpan Pembayaran';
|
||||||
|
@endif
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
@@ -76,10 +76,10 @@
|
|||||||
name="status_bayar" id="status_bayar">
|
name="status_bayar" id="status_bayar">
|
||||||
<option value="">Pilih Status Bayar</option>
|
<option value="">Pilih Status Bayar</option>
|
||||||
<option value="sudah_bayar"
|
<option value="sudah_bayar"
|
||||||
{{ old('status_bayar') == 'sudah_bayar' || (isset($permohonan) && $permohonan->status_bayar == 'sudah_bayar') ? 'selected' : '' }}>
|
{{ old('status_bayar') == 'sudah_bayar' || (isset($permohonan) && $permohonan?->status_bayar == 'sudah_bayar') ? 'selected' : '' }}>
|
||||||
Sudah Bayar</option>
|
Sudah Bayar</option>
|
||||||
<option value="belum_bayar"
|
<option value="belum_bayar"
|
||||||
{{ old('status_bayar') == 'belum_bayar' || (isset($permohonan) && $permohonan->status_bayar == 'belum_bayar') ? 'selected' : '' }}>
|
{{ old('status_bayar') == 'belum_bayar' || (isset($permohonan) && $permohonan?->status_bayar == 'belum_bayar') ? 'selected' : '' }}>
|
||||||
Belum Bayar</option>
|
Belum Bayar</option>
|
||||||
</select>
|
</select>
|
||||||
@error('status_bayar')
|
@error('status_bayar')
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
<div class="flex flex-wrap gap-2.5">
|
<div class="flex flex-wrap gap-2.5">
|
||||||
<div class="h-[24px] border border-r-gray-200"></div>
|
<div class="h-[24px] border border-r-gray-200"></div>
|
||||||
<a class="btn btn-sm btn-light" href="#"> Export to Excel </a>
|
<a class="btn btn-sm btn-light" href="#"> Export to Excel </a>
|
||||||
|
<a class="btn btn-sm btn-primary" href="{{ route('pembayaran.create') }}"> Tambah Pembayaran </a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -212,11 +213,19 @@
|
|||||||
actions: {
|
actions: {
|
||||||
title: 'Status',
|
title: 'Status',
|
||||||
render: (item, data) => {
|
render: (item, data) => {
|
||||||
|
if (data.is_permohonan) {
|
||||||
return `<div class="flex gap-2 justify-center">
|
return `<div class="flex gap-2 justify-center">
|
||||||
<a class="btn btn-icon btn-clear btn-warning" href="pembayaran/${data.id}/edit" title="Lakukan Pembayaran">
|
<a class="btn btn-icon btn-clear btn-warning" href="pembayaran/${data.id}/edit" title="Lakukan Pembayaran">
|
||||||
<i class="ki-outline ki-credit-cart"></i>
|
<i class="ki-outline ki-credit-cart"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
} else {
|
||||||
|
return `<div class="flex gap-2 justify-center">
|
||||||
|
<a class="btn btn-icon btn-clear btn-warning" href="pembayaran/${data.id}/edit?tiket=true" title="Lakukan Pembayaran">
|
||||||
|
<i class="ki-outline ki-credit-cart"></i>
|
||||||
|
</a>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,3 +9,17 @@
|
|||||||
* is assigned the "api" middleware group. Enjoy building your API!
|
* is assigned the "api" middleware group. Enjoy building your API!
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Modules\Lpj\Http\Controllers\Api\DebiturController;
|
||||||
|
|
||||||
|
// Route untuk pencarian debitur (autocomplete)
|
||||||
|
Route::middleware(['web', 'auth'])->group(function () {
|
||||||
|
Route::get('/debitur/search', [DebiturController::class, 'search'])
|
||||||
|
->name('debitur.search');
|
||||||
|
|
||||||
|
// Route untuk get detail debitur berdasarkan code
|
||||||
|
Route::get('/debitur/detail', [DebiturController::class, 'getByCode'])
|
||||||
|
->name('debitur.detail');
|
||||||
|
});
|
||||||
|
|||||||
@@ -109,6 +109,11 @@ Breadcrumbs::for('pembayaran', function (BreadcrumbTrail $trail) {
|
|||||||
$trail->push('Pembayaran', route('pembayaran.index'));
|
$trail->push('Pembayaran', route('pembayaran.index'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Breadcrumbs::for('pembayaran.create', function (BreadcrumbTrail $trail) {
|
||||||
|
$trail->parent('pembayaran');
|
||||||
|
$trail->push('Buat Pembayaran');
|
||||||
|
});
|
||||||
|
|
||||||
Breadcrumbs::for('pembayaran.edit', function (BreadcrumbTrail $trail) {
|
Breadcrumbs::for('pembayaran.edit', function (BreadcrumbTrail $trail) {
|
||||||
$trail->parent('pembayaran');
|
$trail->parent('pembayaran');
|
||||||
$trail->push('Lakukan Pembayaran');
|
$trail->push('Lakukan Pembayaran');
|
||||||
|
|||||||
Reference in New Issue
Block a user