diff --git a/app/Http/Controllers/PrintStatementController.php b/app/Http/Controllers/PrintStatementController.php index ff88fc0..006c740 100644 --- a/app/Http/Controllers/PrintStatementController.php +++ b/app/Http/Controllers/PrintStatementController.php @@ -1,6 +1,6 @@ validated(); + $validated['request_type'] = 'single_account'; // Default untuk request manual + if($validated['branch_code'] && $validated['stmt_sent_type']){ + $validated['request_type'] = 'multi_account'; // Default untuk request manual + } + // Add user tracking data dan field baru untuk single account request $validated['user_id'] = Auth::id(); $validated['created_by'] = Auth::id(); $validated['ip_address'] = $request->ip(); $validated['user_agent'] = $request->userAgent(); - $validated['request_type'] = 'single_account'; // Default untuk request manual + $validated['status'] = 'pending'; // Status awal $validated['authorization_status'] = 'approved'; // Status otorisasi awal $validated['total_accounts'] = 1; // Untuk single account @@ -93,7 +100,7 @@ use ZipArchive; $validated['success_count'] = 0; $validated['failed_count'] = 0; $validated['stmt_sent_type'] = $request->input('stmt_sent_type') ? implode(',', $request->input('stmt_sent_type')) : ''; - $validated['branch_code'] = $branch_code; // Awal tidak tersedia + $validated['branch_code'] = $validated['branch_code'] ?? $branch_code; // Awal tidak tersedia // Create the statement log $statement = PrintStatementLog::create($validated); @@ -109,7 +116,7 @@ use ZipArchive; // Process statement availability check $this->checkStatementAvailability($statement); if(!$statement->is_available){ - $this->printStatementRekening($statement->account_number,$statement->period_from,$statement->period_to,$statement->stmt_sent_type); + $this->printStatementRekening($statement); } $statement = PrintStatementLog::find($statement->id); @@ -784,9 +791,493 @@ use ZipArchive; return "statement_{$accountNumber}_{$statement->period_from}.pdf"; } + /** + * Generate statement view atau PDF berdasarkan parameter + * + * @param string $norek Nomor rekening + * @param string $period Periode dalam format YYYYMM + * @param string|null $format Format output: 'html' atau 'pdf' + * @return \Illuminate\View\View|\Illuminate\Http\Response + */ + public function generated($norek, $period='202505', $format = 'html'){ + try { + DB::beginTransaction(); - function printStatementRekening($accountNumber, $period, $periodTo = null, $stmtSentType = null) { - $period = $period ?? date('Ym'); + Log::info('Generating statement', [ + 'account_number' => $norek, + 'period' => $period, + 'format' => $format, + 'user_id' => Auth::id() + ]); + + $stmtEntries = ProcessedStatement::where(['account_number' => $norek, 'period' => $period])->orderBy('sequence_no')->get(); + $account = Account::with('customer')->where('account_number', $norek)->first(); + + if (!$account) { + throw new Exception("Account not found: {$norek}"); + } + + $customer = $account->customer; + $branch = Branch::where('code', $account->branch_code)->first(); + + // Cek apakah file gambar ada + $headerImagePath = public_path('assets/media/images/bg-header-table.png'); + $headerTableBg = file_exists($headerImagePath) + ? base64_encode(file_get_contents($headerImagePath)) + : null; + + // Logika untuk menentukan period saldo berdasarkan aturan baru + $saldoPeriod = $this->calculateSaldoPeriod($period); + + Log::info('Calculated saldo period', [ + 'original_period' => $period, + 'saldo_period' => $saldoPeriod + ]); + + $saldoAwalBulan = AccountBalance::where(['account_number' => $norek, 'period' => $saldoPeriod])->first(); + + if (!$saldoAwalBulan) { + Log::warning('Saldo awal bulan not found', [ + 'account_number' => $norek, + 'saldo_period' => $saldoPeriod + ]); + $saldoAwalBulan = (object) ['actual_balance' => 0]; + } + + DB::commit(); + + Log::info('Statement data prepared successfully', [ + 'account_number' => $norek, + 'period' => $period, + 'saldo_period' => $saldoPeriod, + 'saldo_awal' => $saldoAwalBulan->actual_balance ?? 0, + 'entries_count' => $stmtEntries->count() + ]); + + $periodDates = calculatePeriodDates($period); + + // Jika format adalah PDF, generate PDF + if ($format === 'pdf') { + return $this->generateStatementPdf($norek, $period, $stmtEntries, $account, $customer, $headerTableBg, $branch, $saldoAwalBulan); + } + + // Default return HTML view + return view('webstatement::statements.stmt', compact('stmtEntries', 'account', 'customer', 'headerTableBg', 'branch', 'period', 'saldoAwalBulan')); + + } catch (Exception $e) { + DB::rollBack(); + + Log::error('Failed to generate statement', [ + 'error' => $e->getMessage(), + 'account_number' => $norek, + 'period' => $period, + 'format' => $format, + 'trace' => $e->getTraceAsString() + ]); + + if ($format === 'pdf') { + return response()->json([ + 'success' => false, + 'message' => 'Failed to generate PDF statement', + 'error' => $e->getMessage() + ], 500); + } + + throw $e; + } + } + + /** + * Generate PDF dari statement HTML dan simpan ke storage + * + * @param string $norek Nomor rekening + * @param string $period Periode + * @param \Illuminate\Database\Eloquent\Collection $stmtEntries Data transaksi + * @param object $account Data akun + * @param object $customer Data customer + * @param string|null $headerTableBg Base64 encoded header image + * @param object $branch Data cabang + * @param object $saldoAwalBulan Data saldo awal + * @return \Illuminate\Http\Response + */ + protected function generateStatementPdf($norek, $period, $stmtEntries, $account, $customer, $headerTableBg, $branch, $saldoAwalBulan) + { + try { + DB::beginTransaction(); + + Log::info('Starting PDF generation with storage', [ + 'account_number' => $norek, + 'period' => $period, + 'user_id' => Auth::id() + ]); + + // Render HTML view + $html = view('webstatement::statements.stmt', compact( + 'stmtEntries', + 'account', + 'customer', + 'headerTableBg', + 'branch', + 'period', + 'saldoAwalBulan' + ))->render(); + + // Generate nama file PDF + $filename = $this->generatePdfFileName($norek, $period); + + // Tentukan path storage + $storagePath = "statements/{$period}/{$norek}"; + $fullStoragePath = "{$storagePath}/{$filename}"; + + // Pastikan direktori storage ada + Storage::disk('local')->makeDirectory($storagePath); + + // Path temporary untuk Browsershot + $tempPath = storage_path("app/{$fullStoragePath}"); + + // Generate PDF menggunakan Browsershot dan simpan langsung ke storage + Browsershot::html($html) + ->showBackground() + ->setOption('addStyleTag', json_encode(['content' => '@page { margin: 0; }'])) + ->format('A4') + ->margins(0, 0, 0, 0) + ->waitUntilNetworkIdle() + ->timeout(60) + ->save($tempPath); + + // Verifikasi file berhasil dibuat + if (!file_exists($tempPath)) { + throw new Exception('PDF file was not created successfully'); + } + + $fileSize = filesize($tempPath); + + Log::info('PDF generated and saved to storage', [ + 'account_number' => $norek, + 'period' => $period, + 'storage_path' => $fullStoragePath, + 'file_size' => $fileSize, + 'temp_path' => $tempPath + ]); + + DB::commit(); + + // Return download response + return response()->download($tempPath, $filename, [ + 'Content-Type' => 'application/pdf', + 'Content-Disposition' => 'attachment; filename="' . $filename . '"' + ])->deleteFileAfterSend(false); // Keep file in storage + + } catch (Exception $e) { + DB::rollBack(); + + Log::error('Failed to generate PDF with storage', [ + 'error' => $e->getMessage(), + 'account_number' => $norek, + 'period' => $period, + 'trace' => $e->getTraceAsString() + ]); + + throw new Exception('Failed to generate PDF: ' . $e->getMessage()); + } + } + + /** + * Generate nama file PDF berdasarkan nomor rekening dan periode + * + * @param string $norek Nomor rekening + * @param string $period Periode + * @return string + */ + protected function generatePdfFileName($norek, $period) + { + try { + $filename = "statement_{$norek}_{$period}.pdf"; + + Log::info('Generated PDF filename', [ + 'account_number' => $norek, + 'period' => $period, + 'filename' => $filename + ]); + + return $filename; + + } catch (Exception $e) { + Log::error('Error generating PDF filename', [ + 'error' => $e->getMessage(), + 'account_number' => $norek, + 'period' => $period + ]); + + // Fallback filename + return "statement_{$norek}_{$period}.pdf"; + } + } + + /** + * Simpan PDF ke storage dengan validasi dan logging + * + * @param string $tempPath Path temporary file + * @param string $storagePath Path di storage + * @param string $norek Nomor rekening + * @param string $period Periode + * @return string|null Path file yang disimpan + */ + protected function savePdfToStorage($tempPath, $storagePath, $norek, $period) + { + try { + // Validasi file temporary ada + if (!file_exists($tempPath)) { + throw new Exception('Temporary PDF file not found'); + } + + // Validasi ukuran file + $fileSize = filesize($tempPath); + if ($fileSize === 0) { + throw new Exception('PDF file is empty'); + } + + // Baca konten file + $pdfContent = file_get_contents($tempPath); + if ($pdfContent === false) { + throw new Exception('Failed to read PDF content'); + } + + // Simpan ke storage + $saved = Storage::disk('local')->put($storagePath, $pdfContent); + + if (!$saved) { + throw new Exception('Failed to save PDF to storage'); + } + + // Verifikasi file tersimpan + if (!Storage::disk('local')->exists($storagePath)) { + throw new Exception('PDF file not found in storage after save'); + } + + $savedSize = Storage::disk('local')->size($storagePath); + + Log::info('PDF successfully saved to storage', [ + 'account_number' => $norek, + 'period' => $period, + 'storage_path' => $storagePath, + 'original_size' => $fileSize, + 'saved_size' => $savedSize, + 'temp_path' => $tempPath + ]); + + return $storagePath; + + } catch (Exception $e) { + Log::error('Failed to save PDF to storage', [ + 'error' => $e->getMessage(), + 'account_number' => $norek, + 'period' => $period, + 'storage_path' => $storagePath, + 'temp_path' => $tempPath + ]); + + return null; + } + } + + /** + * Ambil PDF dari storage untuk download + * + * @param string $norek Nomor rekening + * @param string $period Periode + * @param string $filename Nama file (optional) + * @return \Illuminate\Http\Response + */ + public function downloadFromStorage($norek, $period, $filename = null) + { + try { + // Generate filename jika tidak disediakan + if (!$filename) { + $filename = $this->generatePdfFileName($norek, $period); + } + + $storagePath = "statements/{$period}/{$norek}/{$filename}"; + + // Cek apakah file ada di storage + if (!Storage::disk('local')->exists($storagePath)) { + Log::warning('PDF not found in storage', [ + 'account_number' => $norek, + 'period' => $period, + 'storage_path' => $storagePath + ]); + + return response()->json([ + 'success' => false, + 'message' => 'PDF file not found in storage' + ], 404); + } + + $fullPath = Storage::disk('local')->path($storagePath); + + Log::info('PDF downloaded from storage', [ + 'account_number' => $norek, + 'period' => $period, + 'storage_path' => $storagePath + ]); + + return response()->download($fullPath, $filename, [ + 'Content-Type' => 'application/pdf' + ]); + + } catch (Exception $e) { + Log::error('Failed to download PDF from storage', [ + 'error' => $e->getMessage(), + 'account_number' => $norek, + 'period' => $period, + 'filename' => $filename + ]); + + return response()->json([ + 'success' => false, + 'message' => 'Failed to download PDF', + 'error' => $e->getMessage() + ], 500); + } + } + + /** + * Hapus PDF dari storage + * + * @param string $norek Nomor rekening + * @param string $period Periode + * @param string $filename Nama file (optional) + * @return bool + */ + public function deleteFromStorage($norek, $period, $filename = null) + { + try { + if (!$filename) { + $filename = $this->generatePdfFileName($norek, $period); + } + + $storagePath = "statements/{$period}/{$norek}/{$filename}"; + + if (Storage::disk('local')->exists($storagePath)) { + $deleted = Storage::disk('local')->delete($storagePath); + + Log::info('PDF deleted from storage', [ + 'account_number' => $norek, + 'period' => $period, + 'storage_path' => $storagePath, + 'success' => $deleted + ]); + + return $deleted; + } + + Log::warning('PDF not found for deletion', [ + 'account_number' => $norek, + 'period' => $period, + 'storage_path' => $storagePath + ]); + + return false; + + } catch (Exception $e) { + Log::error('Failed to delete PDF from storage', [ + 'error' => $e->getMessage(), + 'account_number' => $norek, + 'period' => $period, + 'filename' => $filename + ]); + + return false; + } + } + + /** + * Generate PDF dan return sebagai stream untuk preview + * + * @param string $norek Nomor rekening + * @param string $period Periode + * @return \Illuminate\Http\Response + */ + public function previewPdf($norek, $period = '202505') + { + try { + Log::info('Generating PDF preview', [ + 'account_number' => $norek, + 'period' => $period, + 'user_id' => Auth::id() + ]); + + // Generate PDF dengan format parameter + $response = $this->generated($norek, $period, 'pdf'); + + // Ubah header untuk preview di browser + return $response->header('Content-Disposition', 'inline; filename="statement_preview.pdf"'); + + } catch (Exception $e) { + Log::error('Failed to generate PDF preview', [ + 'error' => $e->getMessage(), + 'account_number' => $norek, + 'period' => $period + ]); + + return response()->json([ + 'success' => false, + 'message' => 'Failed to generate PDF preview', + 'error' => $e->getMessage() + ], 500); + } + } + + /** + * Menghitung period untuk pengambilan saldo berdasarkan aturan bisnis + * - Jika period = 202505, gunakan 20250510 + * - Jika period > 202505, ambil tanggal akhir bulan sebelumnya + * + * @param string $period Format YYYYMM + * @return string Format YYYYMMDD + */ + private function calculateSaldoPeriod($period) + { + try { + // Jika period adalah 202505, gunakan tanggal 10 Mei 2025 + if ($period === '202505') { + return '20250510'; + } + + // Jika period lebih dari 202505, ambil tanggal akhir bulan sebelumnya + if ($period > '202505') { + $year = substr($period, 0, 4); + $month = substr($period, 4, 2); + + // Buat tanggal pertama bulan ini + $firstDayOfMonth = Carbon::createFromDate($year, $month, 1); + + // Ambil tanggal terakhir bulan sebelumnya + $lastDayPrevMonth = $firstDayOfMonth->subDay(); + + return $lastDayPrevMonth->format('Ymd'); + } + + // Untuk period sebelum 202505, gunakan logika default (tanggal 10) + $year = substr($period, 0, 4); + $month = substr($period, 4, 2); + + return $year . $month . '10'; + + } catch (Exception $e) { + Log::error('Error calculating saldo period', [ + 'period' => $period, + 'error' => $e->getMessage() + ]); + + // Fallback ke format default + return $period . '10'; + } + } + + function printStatementRekening($statement) { + $accountNumber = $statement->account_number; + $period = $statement->period_from ?? date('Ym'); $balance = AccountBalance::where('account_number', $accountNumber) ->when($period === '202505', function($query) { return $query->where('period', '>=', '20250512') @@ -810,7 +1301,7 @@ use ZipArchive; } // Dispatch the job - $job = ExportStatementPeriodJob::dispatch($accountNumber, $period, $balance, $clientName); + $job = ExportStatementPeriodJob::dispatch($statement, $accountNumber, $period, $balance, $clientName); Log::info("Statement export job dispatched successfully", [ 'job_id' => $job->job_id ?? null, diff --git a/app/Http/Requests/PrintStatementRequest.php b/app/Http/Requests/PrintStatementRequest.php index 45a78c1..c0c98b0 100644 --- a/app/Http/Requests/PrintStatementRequest.php +++ b/app/Http/Requests/PrintStatementRequest.php @@ -21,6 +21,7 @@ class PrintStatementRequest extends FormRequest public function rules(): array { $rules = [ + 'branch_code' => ['required', 'string'], // account_number required jika stmt_sent_type tidak diisi atau kosong 'account_number' => [ function ($attribute, $value, $fail) { @@ -99,6 +100,8 @@ class PrintStatementRequest extends FormRequest public function messages(): array { return [ + 'branch_code.required' => 'Branch code is required', + 'branch_code.string' => 'Branch code must be a string', 'account_number.required' => 'Account number is required when statement type is not specified', 'stmt_sent_type.*.in' => 'Invalid statement type selected', 'period_from.required' => 'Period is required', diff --git a/app/Jobs/ExportStatementPeriodJob.php b/app/Jobs/ExportStatementPeriodJob.php index c6008ee..49b1667 100644 --- a/app/Jobs/ExportStatementPeriodJob.php +++ b/app/Jobs/ExportStatementPeriodJob.php @@ -33,6 +33,7 @@ class ExportStatementPeriodJob implements ShouldQueue protected $startDate; protected $endDate; protected $toCsv; + protected $statement; /** * Create a new job instance. @@ -43,8 +44,9 @@ class ExportStatementPeriodJob implements ShouldQueue * @param string $client * @param string $disk */ - public function __construct(string $account_number, string $period, string $saldo, string $client = '', string $disk = 'local', bool $toCsv = true) + public function __construct($statement, string $account_number, string $period, string $saldo, string $client = '', string $disk = 'local', bool $toCsv = true) { + $this->statement = $statement; $this->account_number = $account_number; $this->period = $period; $this->saldo = $saldo; @@ -170,10 +172,7 @@ class ExportStatementPeriodJob implements ShouldQueue }); if($entry){ - $printLog = PrintStatementLog::where('account_number', $this->account_number) - ->where('period_from', $this->period) - ->latest() - ->first(); + $printLog = PrintStatementLog::find($this->statement->id); if($printLog){ $printLog->update(['is_generated' => true]); } diff --git a/resources/views/statements/index.blade.php b/resources/views/statements/index.blade.php index 2862cb4..ef263cf 100644 --- a/resources/views/statements/index.blade.php +++ b/resources/views/statements/index.blade.php @@ -22,19 +22,19 @@
+
+
+ {{ $branch->name }}
+Kepada
+{{ $account->customer->name }}
+{{ $account->customer->address }}
+{{ $account->customer->district }}
+{{ ($account->customer->city ? $account->customer->city . ' ' : '') . ($account->customer->province ? $account->customer->province . ' ' : '') . ($account->customer->postal_code ?? '') }} +
+Periode Statement : + {{ dateFormat($startDate) }} s/d + {{ dateFormat($endDate) }}
+Nomor Rekening: + {{ $account->account_number }}
+| Tanggal | +Tanggal Valuta |
+ Keterangan | +Referensi | +Debet | +Kredit | +Saldo | +
|---|---|---|---|---|---|---|
| + | + | Saldo Awal Bulan | ++ | + | + | + {{ number_format($saldoAwalBulan->actual_balance, 2, ',', '.') }} + | +
| {{ date('d/m/Y', strtotime($row->transaction_date)) }} | +{{ date('d/m/Y', strtotime($row->actual_date)) }} | +{{ $narrativeLines[0] ?? '' }} | +{{ $row->reference_number }} | +{{ $debit > 0 ? number_format($debit, 2, ',', '.') : '' }} | +{{ $kredit > 0 ? number_format($kredit, 2, ',', '.') : '' }} + | +{{ number_format($saldo, 2, ',', '.') }} | +
| + | + | {{ $narrativeLines[$i] }} | ++ | + | + | + |
| + | + | + | + | + | + | + |
| + | + | Pindah ke Halaman Berikutnya | ++ | + | + | + |
+
+
+ {{ $branch->name }}
+Kepada
+{{ $account->customer->name }}
+{{ $account->customer->address }}
+{{ $account->customer->district }}
+{{ ($account->customer->city ? $account->customer->city . ' ' : '') . ($account->customer->province ? $account->customer->province . ' ' : '') . ($account->customer->postal_code ?? '') }} +
+Periode Statement : + {{ dateFormat($startDate) }} s/d + {{ dateFormat($endDate) }}
+Nomor Rekening: + {{ $account->account_number }}
+| Tanggal | +Tanggal Valuta |
+ Keterangan | +Referensi | +Debet | +Kredit | +Saldo | +
|---|---|---|---|---|---|---|
| + | + | + | + | + | + | + |
| + | + | Total Akhir | ++ | {{ number_format($totalDebit, 2, ',', '.') }} | +{{ number_format($totalKredit, 2, ',', '.') }} + | +{{ number_format($saldo, 2, ',', '.') }} | +