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.
305 lines
14 KiB
PHP
305 lines
14 KiB
PHP
@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">
|
|
<!-- Form Memo Penyelesaian -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title">
|
|
Buat Memo Penyelesaian
|
|
</h3>
|
|
<div class="flex gap-2 items-center">
|
|
<a href="{{ route('memo.index') }}" class="btn btn-sm btn-light">
|
|
<i class="ki-filled ki-black-left"></i>
|
|
Kembali
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-body">
|
|
@if (session('error'))
|
|
<div class="alert alert-danger">
|
|
{{ session('error') }}
|
|
</div>
|
|
@endif
|
|
|
|
<form action="{{ route('memo.store') }}" method="POST" id="memo-form">
|
|
@csrf
|
|
|
|
<!-- Form Input Memo -->
|
|
<div class="grid grid-cols-1 gap-5 mb-7 lg:grid-cols-2">
|
|
<div class="flex flex-col gap-1">
|
|
<label class="text-gray-900 form-label">Nomor Memo <span class="text-red-500">*</span></label>
|
|
<input type="text" name="memo_number" class="input"
|
|
placeholder="Masukkan nomor memo penyelesaian" value="{{ old('memo_number') }}" required>
|
|
@error('memo_number')
|
|
<span class="text-sm text-red-500">{{ $message }}</span>
|
|
@enderror
|
|
</div>
|
|
|
|
<div class="flex flex-col gap-1">
|
|
<label class="text-gray-900 form-label">Tanggal Pembayaran <span
|
|
class="text-red-500">*</span></label>
|
|
<input type="date" name="payment_date" class="input" value="{{ old('payment_date') }}"
|
|
required>
|
|
@error('payment_date')
|
|
<span class="text-sm text-red-500">{{ $message }}</span>
|
|
@enderror
|
|
</div>
|
|
|
|
<div class="flex flex-col gap-1 lg:col-span-2">
|
|
<label class="text-gray-900 form-label">Total Biaya PJ</label>
|
|
<div class="relative">
|
|
<input type="text" id="total-biaya-pj" class="bg-gray-50 input"
|
|
value="Rp {{ number_format($totalBiayaPJ ?? 0, 0, ',', '.') }}" readonly>
|
|
<div class="flex absolute inset-y-0 right-0 items-center pr-3">
|
|
<i class="text-gray-400 ki-filled ki-calculator"></i>
|
|
</div>
|
|
</div>
|
|
<span class="text-sm text-gray-500">Total biaya dari nominal bayar NOC untuk permohonan yang
|
|
dipilih</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hidden field untuk tanggal memo otomatis -->
|
|
<input type="hidden" name="memo_date" value="{{ date('Y-m-d') }}">
|
|
|
|
<!-- Daftar Permohonan yang Dipilih -->
|
|
@if (count($permohonanList) > 0)
|
|
<div class="mb-7">
|
|
<h4 class="mb-4 text-lg font-semibold">Daftar Permohonan yang Akan Diproses</h4>
|
|
<div class="overflow-x-auto">
|
|
<table class="table table-border">
|
|
<thead>
|
|
<tr class="bg-gray-50">
|
|
<th class="w-12 text-center">
|
|
<input type="checkbox" id="select-all" class="checkbox checkbox-sm" checked>
|
|
</th>
|
|
<th>Nomor Registrasi</th>
|
|
<th>Debitur</th>
|
|
<th>Cabang</th>
|
|
<th>AO</th>
|
|
<th>Tujuan Penilaian</th>
|
|
<th>Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach ($permohonanList as $permohonan)
|
|
<tr>
|
|
<td class="text-center">
|
|
<input type="checkbox" name="permohonan_ids[]"
|
|
value="{{ $permohonan->id }}"
|
|
class="checkbox checkbox-sm permohonan-checkbox" checked>
|
|
</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>
|
|
<span class="uppercase badge badge-sm badge-warning">
|
|
{{ $permohonan->status }}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="p-4 mt-4 bg-blue-50 rounded-lg">
|
|
<div class="flex gap-2 items-center">
|
|
<i class="text-blue-600 ki-filled ki-information"></i>
|
|
<span class="font-medium text-blue-800">Informasi:</span>
|
|
</div>
|
|
<p class="mt-1 text-blue-700">
|
|
Total <span id="selected-count">{{ count($permohonanList) }}</span> permohonan akan
|
|
diproses untuk memo penyelesaian.
|
|
Anda dapat menghapus centang pada permohonan yang tidak ingin diproses.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tombol Submit -->
|
|
<div class="flex gap-2 justify-end">
|
|
<a href="{{ route('memo.index') }}" class="btn btn-light">
|
|
Batal
|
|
</a>
|
|
<button type="submit" class="btn btn-primary" id="submit-btn">
|
|
<i class="ki-filled ki-check"></i>
|
|
Buat Memo Penyelesaian
|
|
</button>
|
|
</div>
|
|
@else
|
|
<div class="py-10 text-center">
|
|
<div class="mb-4">
|
|
<i class="text-6xl text-gray-400 ki-filled ki-information-2"></i>
|
|
</div>
|
|
<h4 class="mb-2 text-lg font-semibold text-gray-600">Tidak Ada Data</h4>
|
|
<p class="mb-4 text-gray-500">Tidak ada permohonan yang dipilih untuk diproses.</p>
|
|
<a href="{{ route('memo.index') }}" class="btn btn-primary">
|
|
Kembali ke Daftar
|
|
</a>
|
|
</div>
|
|
@endif
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
|
|
@push('scripts')
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const selectAllCheckbox = document.getElementById('select-all');
|
|
const permohonanCheckboxes = document.querySelectorAll('.permohonan-checkbox');
|
|
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
|
|
*/
|
|
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;
|
|
if (count === 0) {
|
|
submitBtn.innerHTML = '<i class="ki-filled ki-check"></i> Pilih minimal 1 permohonan';
|
|
submitBtn.classList.add('btn-disabled');
|
|
} else {
|
|
submitBtn.innerHTML =
|
|
`<i class="ki-filled ki-check"></i> 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();
|
|
}
|
|
|
|
/**
|
|
* Event listener untuk select all checkbox
|
|
*/
|
|
if (selectAllCheckbox) {
|
|
selectAllCheckbox.addEventListener('change', function() {
|
|
const isChecked = this.checked;
|
|
permohonanCheckboxes.forEach(checkbox => {
|
|
checkbox.checked = isChecked;
|
|
});
|
|
updateSelectedCount();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Event listener untuk individual checkboxes
|
|
*/
|
|
permohonanCheckboxes.forEach(checkbox => {
|
|
checkbox.addEventListener('change', function() {
|
|
updateSelectedCount();
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Event listener untuk form submission
|
|
*/
|
|
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?`
|
|
);
|
|
if (!confirmed) {
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
|
|
// Disable submit button untuk mencegah double submit
|
|
if (submitBtn) {
|
|
submitBtn.disabled = true;
|
|
submitBtn.innerHTML = '<i class="ki-filled ki-loading"></i> Memproses...';
|
|
}
|
|
});
|
|
}
|
|
|
|
// Initialize count dan total biaya PJ
|
|
updateSelectedCount();
|
|
});
|
|
</script>
|
|
@endpush
|