### Perubahan Utama - Tambah validasi untuk memverifikasi bahwa nomor rekening sesuai dengan cabang pengguna. - Cegah transaksi untuk rekening yang terdaftar di cabang khusus (`ID0019999`). - Perbaikan sistem untuk menangani kasus rekening yang tidak ditemukan di database. ### Detail Perubahan 1. **Validasi Cabang Rekening**: - Tambah pengecekan untuk memastikan rekening yang dimasukkan adalah milik cabang pengguna (non-multi-branch). - Blokir transaksi jika rekening terdaftar pada cabang khusus (`ID0019999`) dengan menampilkan pesan error yang relevan. - Tambahkan pesan error jika nomor rekening tidak ditemukan dalam sistem. 2. **Update Logika Penyimpanan**: - Tambahkan validasi untuk mengisi kolom `branch_code` secara otomatis berdasarkan informasi rekening terkait. - Otomatis atur nilai awal `authorization_status` menjadi `approved`. 3. **Penghapusan Atribut Tidak Digunakan**: - Hapus form `branch_code` dari view terkait (`index.blade.php`) karena sekarang diisi secara otomatis berdasarkan data rekening. 4. **Perbaikan View dan Logika Terkait Status Otorisasi**: - Hapus logic dan elemen UI terkait `authorization_status` di halaman statement (`index.blade.php` dan `show.blade.php`). - Simplifikasi tampilan untuk hanya menampilkan informasi yang tersedia dan relevan. 5. **Optimasi Query Data Cabang**: - Update query untuk memfilter cabang berdasarkan kondisi `customer_company` dan mengecualikan kode cabang khusus. 6. **Penyesuaian Struktur Request**: - Hapus validasi terkait `branch_code` di `PrintStatementRequest` karena tidak lagi relevan. 7. **Log Aktivitas dan Kesalahan**: - Tambahkan log untuk mencatat aktivitas seperti validasi rekening dan penyimpanan batch data. - Penanganan lebih baik untuk logging jika terjadi error saat validasi nomor rekening atau penyimpanan statement. ### Manfaat Perubahan - Meningkatkan akurasi data cabang dan validasi rekening sebelum penyimpanan. - Menyederhanakan antarmuka pengguna dengan menghapus field input redundant. - Memastikan proses menjadi lebih transparan dengan penanganan error yang lebih baik. Langkah ini diterapkan untuk meningkatkan keamanan dan keandalan sistem dalam memverifikasi dan memproses pemintaan statement.
329 lines
16 KiB
PHP
329 lines
16 KiB
PHP
@extends('layouts.main')
|
|
|
|
@section('breadcrumbs')
|
|
{{ Breadcrumbs::render(request()->route()->getName()) }}
|
|
@endsection
|
|
|
|
@section('content')
|
|
<div class="grid grid-cols-8 gap-5">
|
|
<div class="col-span-2 card">
|
|
<div class="card-header">
|
|
<h3 class="card-title">Request Print Stetement</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<form action="{{ isset($statement) ? route('statements.update', $statement->id) : route('statements.store') }}" method="POST">
|
|
@csrf
|
|
@if(isset($statement))
|
|
@method('PUT')
|
|
@endif
|
|
|
|
<div class="grid grid-cols-1 gap-5">
|
|
<div class="form-group">
|
|
<label class="form-label required" for="account_number">Account Number</label>
|
|
<input type="text" class="input form-control @error('account_number') is-invalid @enderror" id="account_number" name="account_number" value="{{ old('account_number', $statement->account_number ?? '') }}" required>
|
|
@error('account_number')
|
|
<div class="invalid-feedback">{{ $message }}</div>
|
|
@enderror
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label" for="email">Email</label>
|
|
<input type="email" class="input form-control @error('email') is-invalid @enderror" id="email" name="email" value="{{ old('email', $statement->email ?? '') }}" placeholder="Optional email for send statement">
|
|
@error('email')
|
|
<div class="invalid-feedback">{{ $message }}</div>
|
|
@enderror
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label required" for="start_date">Start Date</label>
|
|
|
|
<input class="input @error('period_from') border-danger bg-danger-light @enderror"
|
|
type="month"
|
|
name="period_from"
|
|
value="{{ $statement->period_from ?? old('period_from') }}"
|
|
max="{{ date('Y-m', strtotime('-1 month')) }}">
|
|
@error('period_from')
|
|
<em class="alert text-danger text-sm">{{ $message }}</em>
|
|
@enderror
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label required" for="end_date">End Date</label>
|
|
<input class="input @error('period_to') border-danger bg-danger-light @enderror"
|
|
type="month"
|
|
name="period_to"
|
|
value="{{ $statement->period_to ?? old('period_to') }}"
|
|
max="{{ date('Y-m', strtotime('-1 month')) }}">
|
|
@error('period_to')
|
|
<em class="alert text-danger text-sm">{{ $message }}</em>
|
|
@enderror
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-5 text-end">
|
|
<button type="reset" class="btn btn-light me-3">Reset</button>
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="ki-outline ki-check fs-2"></i> Submit
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div class="col-span-6">
|
|
<div class="card card-grid min-w-full" data-datatable="false" data-datatable-page-size="10" data-datatable-state-save="false" id="statement-table" data-api-url="{{ route('statements.datatables') }}">
|
|
<div class="card-header py-5 flex-wrap">
|
|
<h3 class="card-title">
|
|
Daftar Statement Request
|
|
</h3>
|
|
<div class="flex flex-wrap gap-2 lg:gap-5">
|
|
<div class="flex">
|
|
<label class="input input-sm"> <i class="ki-filled ki-magnifier"> </i>
|
|
<input placeholder="Search Statement" id="search" type="text" value="">
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="scrollable-x-auto">
|
|
<table class="table table-auto table-border align-middle text-gray-700 font-medium text-sm" data-datatable-table="true">
|
|
<thead>
|
|
<tr>
|
|
<th class="w-14">
|
|
<input class="checkbox checkbox-sm" data-datatable-check="true" type="checkbox"/>
|
|
</th>
|
|
<th class="min-w-[100px]" data-datatable-column="id">
|
|
<span class="sort"> <span class="sort-label"> ID </span>
|
|
<span class="sort-icon"> </span> </span>
|
|
</th>
|
|
<th class="min-w-[150px]" data-datatable-column="branch_name">
|
|
<span class="sort"> <span class="sort-label"> Branch </span>
|
|
<span class="sort-icon"> </span> </span>
|
|
</th>
|
|
<th class="min-w-[150px]" data-datatable-column="account_number">
|
|
<span class="sort"> <span class="sort-label"> Account Number </span>
|
|
<span class="sort-icon"> </span> </span>
|
|
</th>
|
|
<th class="min-w-[150px]" data-datatable-column="period">
|
|
<span class="sort"> <span class="sort-label"> Period </span>
|
|
<span class="sort-icon"> </span> </span>
|
|
</th>
|
|
<th class="min-w-[150px]" data-datatable-column="is_available">
|
|
<span class="sort"> <span class="sort-label"> Available </span>
|
|
<span class="sort-icon"> </span> </span>
|
|
</th>
|
|
<th class="min-w-[150px]" data-datatable-column="remarks">
|
|
<span class="sort"> <span class="sort-label"> Notes </span>
|
|
<span class="sort-icon"> </span> </span>
|
|
</th>
|
|
<th class="min-w-[180px]" data-datatable-column="created_at">
|
|
<span class="sort"> <span class="sort-label"> Created At </span>
|
|
<span class="sort-icon"> </span> </span>
|
|
</th>
|
|
<th class="min-w-[50px] text-center" data-datatable-column="actions">Action</th>
|
|
</tr>
|
|
</thead>
|
|
</table>
|
|
</div>
|
|
<div class="card-footer justify-center md:justify-between flex-col md:flex-row gap-3 text-gray-600 text-2sm font-medium">
|
|
<div class="flex items-center gap-2">
|
|
Show
|
|
<select class="select select-sm w-16" data-datatable-size="true" name="perpage"> </select> per page
|
|
</div>
|
|
<div class="flex items-center gap-4">
|
|
<span data-datatable-info="true"> </span>
|
|
<div class="pagination" data-datatable-pagination="true">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
|
|
@push('scripts')
|
|
<script type="text/javascript">
|
|
function deleteData(data) {
|
|
Swal.fire({
|
|
title: 'Are you sure?',
|
|
text: "You won't be able to revert this!",
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#3085d6',
|
|
cancelButtonColor: '#d33',
|
|
confirmButtonText: 'Yes, delete it!'
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
$.ajaxSetup({
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
|
}
|
|
});
|
|
|
|
$.ajax(`statements/${data}`, {
|
|
type: 'DELETE'
|
|
}).then((response) => {
|
|
swal.fire('Deleted!', 'Statement request has been deleted.', 'success').then(() => {
|
|
window.location.reload();
|
|
});
|
|
}).catch((error) => {
|
|
console.error('Error:', error);
|
|
Swal.fire('Error!', 'An error occurred while deleting the record.', 'error');
|
|
});
|
|
}
|
|
})
|
|
}
|
|
</script>
|
|
|
|
<script type="module">
|
|
const element = document.querySelector('#statement-table');
|
|
const searchInput = document.getElementById('search');
|
|
|
|
const apiUrl = element.getAttribute('data-api-url');
|
|
const dataTableOptions = {
|
|
apiEndpoint: apiUrl,
|
|
pageSize: 10,
|
|
columns: {
|
|
select: {
|
|
render: (item, data, context) => {
|
|
const checkbox = document.createElement('input');
|
|
checkbox.className = 'checkbox checkbox-sm';
|
|
checkbox.type = 'checkbox';
|
|
checkbox.value = data.id.toString();
|
|
checkbox.setAttribute('data-datatable-row-check', 'true');
|
|
return checkbox.outerHTML.trim();
|
|
},
|
|
},
|
|
id: {
|
|
title: 'ID',
|
|
},
|
|
branch_name: {
|
|
title: 'Branch',
|
|
},
|
|
account_number: {
|
|
title: 'Account Number',
|
|
},
|
|
period: {
|
|
title: 'Period',
|
|
render: (item, data) => {
|
|
const monthNames = [
|
|
'January', 'February', 'March', 'April', 'May', 'June',
|
|
'July', 'August', 'September', 'October', 'November', 'December'
|
|
];
|
|
|
|
const formatPeriod = (period) => {
|
|
if (!period) return '';
|
|
const year = period.substring(0, 4);
|
|
const month = parseInt(period.substring(4, 6));
|
|
return `${monthNames[month - 1]} ${year}`;
|
|
};
|
|
|
|
const fromPeriod = formatPeriod(data.period_from);
|
|
const toPeriod = data.period_to ? ` - ${formatPeriod(data.period_to)}` : '';
|
|
|
|
return fromPeriod + toPeriod;
|
|
},
|
|
},
|
|
is_available: {
|
|
title: 'Available',
|
|
render: (item, data) => {
|
|
let statusClass = data.is_available ? 'badge badge-light-success' : 'badge badge-light-danger';
|
|
let statusText = data.is_available ? 'Yes' : 'No';
|
|
return `<span class="${statusClass}">${statusText}</span>`;
|
|
},
|
|
},
|
|
remarks : {
|
|
title: 'Notes',
|
|
},
|
|
created_at: {
|
|
title: 'Created At',
|
|
render: (item, data) => {
|
|
return data.created_at ?? '';
|
|
},
|
|
},
|
|
actions: {
|
|
title: 'Actions',
|
|
render: (item, data) => {
|
|
let buttons = `<div class="flex flex-nowrap justify-center">
|
|
<a class="btn btn-sm btn-icon btn-clear btn-info" href="statements/${data.id}">
|
|
<i class="ki-outline ki-eye"></i>
|
|
</a>`;
|
|
|
|
// Show download button if statement is approved and available but not downloaded
|
|
//if (data.authorization_status === 'approved' && data.is_available && !data.is_downloaded) {
|
|
if (data.is_available) {
|
|
buttons += `<a class="btn btn-sm btn-icon btn-clear btn-success" href="statements/${data.id}/download">
|
|
<i class="ki-outline ki-cloud-download"></i>
|
|
</a>`;
|
|
}
|
|
|
|
// Show send email button if email is not empty and statement is available
|
|
if (data.is_available && data.email) {
|
|
buttons += `<a class="btn btn-sm btn-icon btn-clear btn-primary" href="statements/${data.id}/send-email" title="Send to Email">
|
|
<i class="ki-outline ki-paper-plane"></i>
|
|
</a>`;
|
|
}
|
|
|
|
// Only show delete button if status is pending
|
|
if (data.authorization_status === 'pending') {
|
|
buttons += `<a onclick="deleteData(${data.id})" class="delete btn btn-sm btn-icon btn-clear btn-danger">
|
|
<i class="ki-outline ki-trash"></i>
|
|
</a>`;
|
|
}
|
|
|
|
buttons += `</div>`;
|
|
return buttons;
|
|
},
|
|
}
|
|
},
|
|
};
|
|
|
|
let dataTable = new KTDataTable(element, dataTableOptions);
|
|
// Custom search functionality
|
|
searchInput.addEventListener('input', function () {
|
|
const searchValue = this.value.trim();
|
|
dataTable.search(searchValue, true);
|
|
});
|
|
|
|
// Handle the "select all" checkbox
|
|
const selectAllCheckbox = document.querySelector('input[data-datatable-check="true"]');
|
|
if (selectAllCheckbox) {
|
|
selectAllCheckbox.addEventListener('change', function() {
|
|
const isChecked = this.checked;
|
|
const rowCheckboxes = document.querySelectorAll('input[data-datatable-row-check="true"]');
|
|
|
|
rowCheckboxes.forEach(checkbox => {
|
|
checkbox.checked = isChecked;
|
|
});
|
|
});
|
|
}
|
|
</script>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Validate that end date is after start date
|
|
const startDateInput = document.getElementById('start_date');
|
|
const endDateInput = document.getElementById('end_date');
|
|
|
|
function validateDates() {
|
|
const startDate = new Date(startDateInput.value);
|
|
const endDate = new Date(endDateInput.value);
|
|
|
|
if (startDate > endDate) {
|
|
endDateInput.setCustomValidity('End date must be after start date');
|
|
} else {
|
|
endDateInput.setCustomValidity('');
|
|
}
|
|
}
|
|
|
|
startDateInput.addEventListener('change', validateDates);
|
|
endDateInput.addEventListener('change', validateDates);
|
|
|
|
// Set max date for date inputs to today
|
|
const today = new Date().toISOString().split('T')[0];
|
|
startDateInput.setAttribute('max', today);
|
|
endDateInput.setAttribute('max', today);
|
|
});
|
|
</script>
|
|
@endpush
|