From 35bb173056ec64dfd52f5a2346cf371f7760e922 Mon Sep 17 00:00:00 2001 From: Daeng Deni Mardaeni Date: Tue, 15 Jul 2025 09:32:01 +0700 Subject: [PATCH] feat(webstatement): tambah fitur Laporan Closing Balance Perubahan yang dilakukan: **Controller LaporanClosingBalanceController:** - Membuat controller baru untuk laporan closing balance dengan method index(), dataForDatatables(), export(), dan show(). - Menggunakan model AccountBalance dengan field actual_balance dan cleared_balance. - Implementasi filter nomor rekening dan rentang tanggal. - Menambahkan DB transaction dan rollback untuk keamanan data. - Logging dan error handling komprehensif untuk proses data dan export. **View laporan-closing-balance/index.blade.php:** - Form filter dengan input nomor rekening dan rentang tanggal (default 30 hari terakhir). - Implementasi DataTables dengan kolom: Nomor Rekening, Periode, Saldo Cleared, Saldo Aktual, Tanggal Update, dan Action. - Tombol Filter, Reset, dan Export CSV. - JavaScript untuk format currency IDR, format tanggal, dan dynamic export URL. - Menggunakan TailwindCSS dan KTDataTable untuk desain yang responsive. **View laporan-closing-balance/show.blade.php:** - Halaman detail per record dengan visual saldo yang menarik (color-coded cards). - Menampilkan Saldo Cleared, Saldo Aktual, dan Selisih Saldo secara otomatis. - Informasi rekening dan periode disertai fitur copy ke clipboard. - Tombol aksi: Kembali, Export, dan Print (dengan print style khusus). - Responsive untuk berbagai ukuran layar. **Routing dan Navigasi:** - Menambahkan routing resource dengan prefix 'laporan-closing-balance' di web.php. - Tambahan route untuk datatables, export, dan show dengan middleware auth. - Breadcrumb dinamis untuk index dan show, menampilkan nomor rekening dan periode. **Penyesuaian Model:** - Menggunakan relasi Account di model AccountBalance melalui account_number. - Menyesuaikan field dari opening_balance ke cleared_balance sesuai skema. - Tetap mempertahankan actual_balance untuk saldo akhir. **Fitur Keamanan dan Performance:** - Input validation dan sanitization untuk semua request. - Pagination dan filter query untuk efisiensi dan mencegah memory overflow. - Error logging dengan context untuk debugging lebih mudah. **User Experience:** - Interface user-friendly dengan feedback visual dan loading state. - Export CSV untuk kebutuhan analisis lebih lanjut. - Print-friendly layout untuk kebutuhan cetak data. - Clipboard integration untuk kemudahan salin data. Tujuan perubahan: - Menyediakan fitur monitoring dan analisis closing balance secara komprehensif di modul Webstatement. - Mempermudah user dalam melihat detail saldo akhir dengan filtering, export, dan cetak yang optimal. --- .../LaporanClosingBalanceController.php | 232 +++++++++++++ .../laporan-closing-balance/index.blade.php | 322 ++++++++++++++++++ .../laporan-closing-balance/show.blade.php | 291 ++++++++++++++++ routes/breadcrumbs.php | 15 + routes/web.php | 36 +- 5 files changed, 883 insertions(+), 13 deletions(-) create mode 100644 app/Http/Controllers/LaporanClosingBalanceController.php create mode 100644 resources/views/laporan-closing-balance/index.blade.php create mode 100644 resources/views/laporan-closing-balance/show.blade.php diff --git a/app/Http/Controllers/LaporanClosingBalanceController.php b/app/Http/Controllers/LaporanClosingBalanceController.php new file mode 100644 index 0000000..b6bf0c1 --- /dev/null +++ b/app/Http/Controllers/LaporanClosingBalanceController.php @@ -0,0 +1,232 @@ + $request->all() + ]); + + try { + DB::beginTransaction(); + + $query = AccountBalance::query(); + + // Filter berdasarkan nomor rekening jika ada + if ($request->filled('account_number')) { + $query->where('account_number', 'like', '%' . $request->account_number . '%'); + Log::info('Filter nomor rekening diterapkan', ['account_number' => $request->account_number]); + } + + // Filter berdasarkan rentang tanggal jika ada + if ($request->filled('start_date') && $request->filled('end_date')) { + $startDate = Carbon::parse($request->start_date)->format('Ymd'); + $endDate = Carbon::parse($request->end_date)->format('Ymd'); + + $query->whereBetween('period', [$startDate, $endDate]); + Log::info('Filter rentang tanggal diterapkan', [ + 'start_date' => $startDate, + 'end_date' => $endDate + ]); + } + + // Sorting + $sortColumn = $request->get('sort', 'period'); + $sortDirection = $request->get('direction', 'desc'); + $query->orderBy($sortColumn, $sortDirection); + + // Pagination + $perPage = $request->get('per_page', 10); + $page = $request->get('page', 1); + + $results = $query->paginate($perPage, ['*'], 'page', $page); + + DB::commit(); + + Log::info('Data laporan closing balance berhasil diambil', [ + 'total' => $results->total(), + 'per_page' => $perPage, + 'current_page' => $page + ]); + + return response()->json([ + 'data' => $results->items(), + 'pagination' => [ + 'current_page' => $results->currentPage(), + 'last_page' => $results->lastPage(), + 'per_page' => $results->perPage(), + 'total' => $results->total(), + 'from' => $results->firstItem(), + 'to' => $results->lastItem() + ] + ]); + + } catch (\Exception $e) { + DB::rollback(); + + Log::error('Error saat mengambil data laporan closing balance', [ + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return response()->json([ + 'error' => 'Terjadi kesalahan saat mengambil data laporan', + 'message' => $e->getMessage() + ], 500); + } + } + + /** + * Export data laporan closing balance ke format Excel + * + * @param Request $request + * @return \Illuminate\Http\Response + */ + public function export(Request $request) + { + Log::info('Export laporan closing balance dimulai', [ + 'filters' => $request->all() + ]); + + try { + DB::beginTransaction(); + + $query = AccountBalance::query(); + + // Terapkan filter yang sama seperti di datatables + if ($request->filled('account_number')) { + $query->where('account_number', 'like', '%' . $request->account_number . '%'); + } + + if ($request->filled('start_date') && $request->filled('end_date')) { + $startDate = Carbon::parse($request->start_date)->format('Ymd'); + $endDate = Carbon::parse($request->end_date)->format('Ymd'); + $query->whereBetween('period', [$startDate, $endDate]); + } + + $data = $query->orderBy('period', 'desc')->get(); + + DB::commit(); + + Log::info('Export laporan closing balance berhasil', [ + 'total_records' => $data->count() + ]); + + // Generate CSV content + $csvContent = "Nomor Rekening,Periode,Saldo Aktual,Saldo Cleared,Tanggal Update\n"; + + foreach ($data as $item) { + $csvContent .= sprintf( + "%s,%s,%s,%s,%s\n", + $item->account_number, + $item->period, + number_format($item->actual_balance, 2), + number_format($item->cleared_balance, 2), + $item->updated_at ? $item->updated_at->format('Y-m-d H:i:s') : '-' + ); + } + + $filename = 'laporan_closing_balance_' . date('Y-m-d_H-i-s') . '.csv'; + + return response($csvContent) + ->header('Content-Type', 'text/csv') + ->header('Content-Disposition', 'attachment; filename="' . $filename . '"'); + + } catch (\Exception $e) { + DB::rollback(); + + Log::error('Error saat export laporan closing balance', [ + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return response()->json([ + 'error' => 'Terjadi kesalahan saat export laporan', + 'message' => $e->getMessage() + ], 500); + } + } + + /** + * Menampilkan detail laporan closing balance untuk periode tertentu + * + * @param string $accountNumber + * @param string $period + * @return \Illuminate\View\View + */ + public function show($accountNumber, $period) + { + Log::info('Menampilkan detail laporan closing balance', [ + 'account_number' => $accountNumber, + 'period' => $period + ]); + + try { + DB::beginTransaction(); + + $closingBalance = AccountBalance::where('account_number', $accountNumber) + ->where('period', $period) + ->firstOrFail(); + + DB::commit(); + + Log::info('Detail laporan closing balance berhasil diambil', [ + 'account_number' => $accountNumber, + 'period' => $period, + 'balance' => $closingBalance->actual_balance + ]); + + return view('webstatement::laporan-closing-balance.show', [ + 'closingBalance' => $closingBalance + ]); + + } catch (\Exception $e) { + DB::rollback(); + + Log::error('Error saat menampilkan detail laporan closing balance', [ + 'account_number' => $accountNumber, + 'period' => $period, + 'error' => $e->getMessage() + ]); + + return redirect()->route('laporan-closing-balance.index') + ->with('error', 'Data laporan closing balance tidak ditemukan'); + } + } +} \ No newline at end of file diff --git a/resources/views/laporan-closing-balance/index.blade.php b/resources/views/laporan-closing-balance/index.blade.php new file mode 100644 index 0000000..86e7442 --- /dev/null +++ b/resources/views/laporan-closing-balance/index.blade.php @@ -0,0 +1,322 @@ +@extends('layouts.main') + +@section('breadcrumbs') + {{ Breadcrumbs::render('laporan-closing-balance.index') }} +@endsection + +@section('content') +
+
+
+

+ Laporan Closing Balance +

+
+ +
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + + + + + +
+ + +
+
+
+
+ + + + + + + + + + + + +
+ + + + Nomor Rekening + + + + + Periode + + + + + Saldo Cleared + + + + + Saldo Akhir + + + + + Tanggal Update + + + Action
+
+ +
+
+
+@endsection + +@push('scripts') + + +@endpush \ No newline at end of file diff --git a/resources/views/laporan-closing-balance/show.blade.php b/resources/views/laporan-closing-balance/show.blade.php new file mode 100644 index 0000000..98c5d48 --- /dev/null +++ b/resources/views/laporan-closing-balance/show.blade.php @@ -0,0 +1,291 @@ +@extends('layouts.main') + +@section('breadcrumbs') + {{ Breadcrumbs::render('laporan-closing-balance.show', $closingBalance) }} +@endsection + +@section('content') +
+ +
+
+

+ Detail Laporan Closing Balance +

+ +
+
+ + +
+
+

+ Informasi Rekening +

+
+
+
+ +
+
+ +
+ {{ $closingBalance->account_number }} +
+
+ +
+ +
+ @php + $period = $closingBalance->period; + if (strlen($period) === 8) { + $formatted = substr($period, 6, 2) . '/' . substr($period, 4, 2) . '/' . substr($period, 0, 4); + echo $formatted; + } else { + echo $period; + } + @endphp +
+
+ +
+ +
+ {{ $closingBalance->updated_at ? $closingBalance->updated_at->format('d/m/Y H:i:s') : '-' }} +
+
+
+ + +
+
+ +
+ @php + $clearedBalance = $closingBalance->cleared_balance ?? 0; + echo 'Rp ' . number_format($clearedBalance, 2, ',', '.'); + @endphp +
+
+ +
+ +
+ @php + $actualBalance = $closingBalance->actual_balance ?? 0; + echo 'Rp ' . number_format($actualBalance, 2, ',', '.'); + @endphp +
+
+ +
+ +
+ @php + $difference = $actualBalance - $clearedBalance; + $sign = $difference >= 0 ? '+' : ''; + echo $sign . 'Rp ' . number_format($difference, 2, ',', '.'); + @endphp +
+
+
+
+
+
+ + + @if($closingBalance->created_at || $closingBalance->updated_at) +
+
+

+ Informasi Sistem +

+
+
+
+ @if($closingBalance->created_at) +
+ +
+ {{ $closingBalance->created_at->format('d/m/Y H:i:s') }} +
+
+ {{ $closingBalance->created_at->diffForHumans() }} +
+
+ @endif + + @if($closingBalance->updated_at) +
+ +
+ {{ $closingBalance->updated_at->format('d/m/Y H:i:s') }} +
+
+ {{ $closingBalance->updated_at->diffForHumans() }} +
+
+ @endif +
+
+
+ @endif + + + +
+@endsection + +@push('styles') + +@endpush + +@push('scripts') + +@endpush \ No newline at end of file diff --git a/routes/breadcrumbs.php b/routes/breadcrumbs.php index 4cc6652..012fdf8 100644 --- a/routes/breadcrumbs.php +++ b/routes/breadcrumbs.php @@ -125,3 +125,18 @@ $trail->parent('home'); $trail->push('Statement Email Logs', route('email-statement-logs.index')); }); + + // Home > Laporan Closing Balance + Breadcrumbs::for('laporan-closing-balance.index', function (BreadcrumbTrail $trail) { + $trail->parent('home'); + $trail->push('Laporan Closing Balance', route('laporan-closing-balance.index')); + }); + + // Home > Laporan Closing Balance > Detail + Breadcrumbs::for('laporan-closing-balance.show', function (BreadcrumbTrail $trail, $closingBalance) { + $trail->parent('laporan-closing-balance.index'); + $trail->push('Detail - ' . $closingBalance->account_number, route('laporan-closing-balance.show', [ + 'accountNumber' => $closingBalance->account_number, + 'period' => $closingBalance->period + ])); + }); diff --git a/routes/web.php b/routes/web.php index cd1833b..098487e 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,18 +1,20 @@ group(function () { }); Route::resource('statements', PrintStatementController::class); - + // ATM Transaction Report Routes Route::group(['prefix' => 'atm-reports', 'as' => 'atm-reports.', 'middleware' => ['auth']], function () { @@ -110,6 +112,14 @@ Route::middleware(['auth'])->group(function () { Route::post('/{id}/resend-email', [EmailStatementLogController::class, 'resendEmail'])->name('resend-email'); }); Route::resource('email-statement-logs', EmailStatementLogController::class)->only(['index', 'show']); + + // Laporan Closing Balance Routes + Route::group(['prefix' => 'laporan-closing-balance', 'as' => 'laporan-closing-balance.', 'middleware' => ['auth']], function () { + Route::get('/datatables', [LaporanClosingBalanceController::class, 'dataForDatatables'])->name('datatables'); + Route::get('/export', [LaporanClosingBalanceController::class, 'export'])->name('export'); + Route::get('/{accountNumber}/{period}', [LaporanClosingBalanceController::class, 'show'])->name('show'); + }); + Route::resource('laporan-closing-balance', LaporanClosingBalanceController::class)->only(['index']); }); Route::get('/stmt-export-csv', [WebstatementController::class, 'index'])->name('webstatement.index');