From c1a173c8f75a18d05efe419a7db19e5a61056627 Mon Sep 17 00:00:00 2001 From: Daeng Deni Mardaeni Date: Thu, 10 Jul 2025 09:12:16 +0700 Subject: [PATCH] feat(webstatement): tambah optimasi pemrosesan multi_account dan validasi statement Perubahan yang dilakukan: - Memodifikasi PrintStatementController untuk mendukung request_type baru: multi_account. - Menambahkan validasi stmt_sent_type dan branch_code khusus pada request multi_account. - Menambahkan pengecekan branch_id: ID0019999 dengan penanganan error yang lebih spesifik. - Menambahkan metode processMultiAccountStatement untuk pemrosesan berdasarkan branch_code dan stmt_sent_type. Optimasi PDF: - Melakukan refaktor pada GenerateMultiAccountPdfJob agar mendukung kalkulasi tanggal dinamis (startDate dan endDate). - Mengimplementasikan Browsershot untuk opsi tambahan background dan optimasi waktu proses. - Menambahkan validasi status dan update log pada PrintStatementLog setelah PDF berhasil dibuat. - Menambahkan penanganan penggunaan memori secara granular untuk proses batch PDF dan pembersihan resource otomatis. Logging dan Validasi: - Menambahkan logging pada proses kalkulasi tanggal multi_account. - Logging tambahan dan rollback untuk error yang terjadi saat proses statement atau PDF. - Mengubah penggunaan Auth:: untuk konsistensi role checking. - Mengubah validasi stmt_sent_type dari JSON menjadi array dengan implode(). UI dan Output: - Memodifikasi blade template agar mendukung tampilan stmt_sent_type untuk kasus multi_account. - Menambahkan logika kolom dinamis berdasarkan account_number atau stmt_sent_type. Refaktor umum: - Memisahkan logika antara single dan multi account di PrintStatementController. - Perbaikan minor pada query SQL untuk entri ProcessedStatement. Tujuan perubahan: - Mendukung pemrosesan batch statement multi account secara lebih efisien dan terstruktur. - Menjamin validasi dan logging yang lebih kuat. - Meningkatkan performa pembuatan PDF dan kontrol terhadap penggunaan resource. Signed-off-by: Daeng Deni Mardaeni --- .../Controllers/PrintStatementController.php | 92 ++-- app/Jobs/GenerateMultiAccountPdfJob.php | 471 ++++++++++++++++-- resources/views/statements/index.blade.php | 6 + 3 files changed, 482 insertions(+), 87 deletions(-) diff --git a/app/Http/Controllers/PrintStatementController.php b/app/Http/Controllers/PrintStatementController.php index fd6769a..9dc09d3 100644 --- a/app/Http/Controllers/PrintStatementController.php +++ b/app/Http/Controllers/PrintStatementController.php @@ -45,33 +45,45 @@ ini_set('max_execution_time', 300000); // Add account verification before storing $accountNumber = $request->input('account_number'); // Assuming this is the field name for account number // First, check if the account exists and get branch information - $account = Account::where('account_number', $accountNumber)->first(); - if ($account) { - $branch_code = $account->branch_code; - $userBranchId = session('branch_id'); // Assuming branch ID is stored in session - $multiBranch = session('MULTI_BRANCH'); + $request_type = "single_account"; + if($request->input('branch_code') && !empty($request->input('stmt_sent_type'))){ + $request_type = 'multi_account'; // Default untuk request manual + } + + if($request_type=='single_account'){ + $account = Account::where('account_number', $accountNumber)->first(); + if ($account) { + $branch_code = $account->branch_code; + $userBranchId = session('branch_id'); // Assuming branch ID is stored in session + $multiBranch = session('MULTI_BRANCH'); - if (!$multiBranch) { - // Check if account branch matches user's branch - if ($account->branch_id !== $userBranchId) { - return redirect()->route('statements.index') - ->with('error', 'Nomor rekening tidak sesuai dengan cabang Anda. Transaksi tidak dapat dilanjutkan.'); + if (!$multiBranch) { + // Check if account branch matches user's branch + if ($account->branch_id !== $userBranchId) { + return redirect()->route('statements.index') + ->with('error', 'Nomor rekening tidak sesuai dengan cabang Anda. Transaksi tidak dapat dilanjutkan.'); + } } - } - // Check if account belongs to restricted branch ID0019999 - if ($account->branch_id === 'ID0019999') { + // Check if account belongs to restricted branch ID0019999 + if ($account->branch_id === 'ID0019999') { + return redirect()->route('statements.index') + ->with('error', 'Nomor rekening terdaftar pada cabang khusus. Silakan hubungi bagian HC untuk informasi lebih lanjut.'); + } + + // If all checks pass, proceed with storing data + // Your existing store logic here + + } else { + // Account not found return redirect()->route('statements.index') - ->with('error', 'Nomor rekening terdaftar pada cabang khusus. Silakan hubungi bagian HC untuk informasi lebih lanjut.'); + ->with('error', 'Nomor rekening tidak ditemukan dalam sistem.'); } - - // If all checks pass, proceed with storing data - // Your existing store logic here - } else { - // Account not found - return redirect()->route('statements.index') - ->with('error', 'Nomor rekening tidak ditemukan dalam sistem.'); + if($request->input('branch_code')=== 'ID0019999') { + return redirect()->route('statements.index') + ->with('error', 'tidak dapat dilakukan print statement unruk cabang khusus. Silakan hubungi bagian HC untuk informasi lebih lanjut.'); + } } DB::beginTransaction(); @@ -96,7 +108,7 @@ ini_set('max_execution_time', 300000); $validated['processed_accounts'] = 0; $validated['success_count'] = 0; $validated['failed_count'] = 0; - $validated['stmt_sent_type'] = $request->input('stmt_sent_type') ? json_encode($request->input('stmt_sent_type')) : ''; + $validated['stmt_sent_type'] = $request->input('stmt_sent_type') ? implode(",",$request->input('stmt_sent_type')) : ''; $validated['branch_code'] = $validated['branch_code'] ?? $branch_code; // Awal tidak tersedia // Create the statement log @@ -116,10 +128,9 @@ ini_set('max_execution_time', 300000); $this->printStatementRekening($statement); } - $statement = PrintStatementLog::find($statement->id); - if($statement->email){ - $this->sendEmail($statement->id); - } + //if($statement->email){ + // $this->sendEmail($statement->id); + //} DB::commit(); return redirect()->route('statements.index') @@ -428,7 +439,10 @@ ini_set('max_execution_time', 300000); 'file_path' => $filePath ]); - return $disk->download($filePath, "{$statement->account_number}_{$statement->period_from}.pdf"); + return response()->download( + $disk->path($filePath), + "{$statement->account_number}_{$statement->period_from}.pdf" + ); } else { Log::warning('Statement file not found', [ 'statement_id' => $statement->id, @@ -489,7 +503,7 @@ ini_set('max_execution_time', 300000); $query = PrintStatementLog::query(); $query->whereNotNull('user_id'); - if (!auth()->user()->hasRole('administrator')) { + if (!Auth::user()->role === 'administrator') { $query->where(function($q) { $q->where('user_id', Auth::id()) ->orWhere('branch_code', Auth::user()->branch->code); @@ -591,6 +605,7 @@ ini_set('max_execution_time', 300000); 'authorized_by' => $item->authorizer ? $item->authorizer->name : null, 'authorized_at' => $item->authorized_at ? $item->authorized_at->format('Y-m-d H:i:s') : null, 'remarks' => $item->remarks, + 'request_type' => $item->request_type ?? 'N/A', ]; }); @@ -1274,6 +1289,7 @@ ini_set('max_execution_time', 300000); 'branch_code' => $statement->branch_code ]); + if ($statement->request_type === 'multi_account') { return $this->processMultiAccountStatement($statement); } else { @@ -1307,7 +1323,6 @@ ini_set('max_execution_time', 300000); { try { $period = $statement->period_from ?? date('Ym'); - $clientName = 'client1'; // Validasi stmt_sent_type if (empty($statement->stmt_sent_type)) { @@ -1315,13 +1330,7 @@ ini_set('max_execution_time', 300000); } // Decode stmt_sent_type jika dalam format JSON array - $stmtSentTypes = is_string($statement->stmt_sent_type) - ? json_decode($statement->stmt_sent_type, true) - : $statement->stmt_sent_type; - - if (!is_array($stmtSentTypes)) { - $stmtSentTypes = [$stmtSentTypes]; - } + $stmtSentTypes = explode(',', $statement->stmt_sent_type); Log::info('Processing multi account statement', [ 'statement_id' => $statement->id, @@ -1330,12 +1339,16 @@ ini_set('max_execution_time', 300000); 'period' => $period ]); + + + $clientName = $statement->branch_code.'_'.$period.'_';//.implode('_'.$stmtSentTypes); // Ambil accounts berdasarkan branch_code dan stmt_sent_type $accounts = Account::where('branch_code', $statement->branch_code) ->whereIn('stmt_sent_type', $stmtSentTypes) ->with('customer') + ->limit(5) ->get(); - + if ($accounts->isEmpty()) { throw new \Exception('No accounts found for the specified criteria'); } @@ -1348,8 +1361,9 @@ ini_set('max_execution_time', 300000); // Update statement log dengan informasi accounts $accountNumbers = $accounts->pluck('account_number')->toArray(); + $statement->update([ - 'target_accounts' => $accountNumbers, + 'target_accounts' => implode(",",$accountNumbers), 'total_accounts' => $accounts->count(), 'status' => 'processing', 'started_at' => now() @@ -1522,7 +1536,7 @@ ini_set('max_execution_time', 300000); Log::info('Multi account ZIP downloaded', [ 'statement_id' => $statementId, 'zip_file' => $zipFile, - 'user_id' => auth()->id() + 'user_id' => Auth::id() ]); return response()->download($zipPath, $filename, [ diff --git a/app/Jobs/GenerateMultiAccountPdfJob.php b/app/Jobs/GenerateMultiAccountPdfJob.php index c4c9647..e95080f 100644 --- a/app/Jobs/GenerateMultiAccountPdfJob.php +++ b/app/Jobs/GenerateMultiAccountPdfJob.php @@ -2,22 +2,31 @@ namespace Modules\Webstatement\Jobs; -use Carbon\Carbon; use Exception; +use ZipArchive; +use Carbon\Carbon; use Illuminate\Bus\Queueable; +use Illuminate\Support\Facades\{ + DB, + Log, + Storage +}; +use Spatie\Browsershot\Browsershot; +use Modules\Basicdata\Models\Branch; +use Illuminate\Queue\{ + SerializesModels, + InteractsWithQueue +}; +use Modules\Webstatement\Models\{ + StmtEntry, + AccountBalance, + PrintStatementLog, + ProcessedStatement, + TempStmtNarrParam, + TempStmtNarrFormat +}; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Log; -use Illuminate\Support\Facades\Storage; -use Modules\Webstatement\Models\PrintStatementLog; -use Modules\Webstatement\Models\AccountBalance; -use Modules\Webstatement\Models\StmtEntry; -use Modules\Basicdata\Models\Branch; -use Spatie\Browsershot\Browsershot; -use ZipArchive; class GenerateMultiAccountPdfJob implements ShouldQueue { @@ -28,6 +37,8 @@ class GenerateMultiAccountPdfJob implements ShouldQueue protected $period; protected $clientName; protected $chunkSize = 10; // Process 10 accounts at a time + protected $startDate; + protected $endDate; /** * Create a new job instance. @@ -43,6 +54,36 @@ class GenerateMultiAccountPdfJob implements ShouldQueue $this->accounts = $accounts; $this->period = $period; $this->clientName = $clientName; + + // Calculate period dates using same logic as ExportStatementPeriodJob + $this->calculatePeriodDates(); + } + + /** + * Calculate start and end dates for the given period + * Menggunakan logika yang sama dengan ExportStatementPeriodJob + */ + private function calculatePeriodDates(): void + { + $year = substr($this->period, 0, 4); + $month = substr($this->period, 4, 2); + + // Special case for May 2025 - start from 12th + if ($this->period === '202505') { + $this->startDate = Carbon::createFromDate($year, $month, 12)->startOfDay(); + } else { + // For all other periods, start from 1st of the month + $this->startDate = Carbon::createFromDate($year, $month, 1)->startOfDay(); + } + + // End date is always the last day of the month + $this->endDate = Carbon::createFromDate($year, $month, 1)->endOfMonth()->endOfDay(); + + Log::info('Period dates calculated for PDF generation', [ + 'period' => $this->period, + 'start_date' => $this->startDate->format('Y-m-d'), + 'end_date' => $this->endDate->format('Y-m-d') + ]); } /** @@ -51,12 +92,12 @@ class GenerateMultiAccountPdfJob implements ShouldQueue public function handle(): void { try { - DB::beginTransaction(); Log::info('Starting multi account PDF generation', [ 'statement_id' => $this->statement->id, 'total_accounts' => $this->accounts->count(), - 'period' => $this->period + 'period' => $this->period, + 'date_range' => $this->startDate->format('Y-m-d') . ' to ' . $this->endDate->format('Y-m-d') ]); $pdfFiles = []; @@ -77,6 +118,9 @@ class GenerateMultiAccountPdfJob implements ShouldQueue 'pdf_path' => $pdfPath ]); } + + // Memory cleanup after each account + gc_collect_cycles(); } catch (Exception $e) { $failedCount++; $errors[] = [ @@ -105,11 +149,11 @@ class GenerateMultiAccountPdfJob implements ShouldQueue 'status' => $failedCount > 0 ? 'completed_with_errors' : 'completed', 'completed_at' => now(), 'is_available' => $zipPath ? true : false, + 'is_generated' => $zipPath ? true : false, 'error_message' => !empty($errors) ? json_encode($errors) : null ]); - DB::commit(); - + Log::info('Multi account PDF generation completed', [ 'statement_id' => $this->statement->id, 'success_count' => $successCount, @@ -118,7 +162,6 @@ class GenerateMultiAccountPdfJob implements ShouldQueue ]); } catch (Exception $e) { - DB::rollBack(); Log::error('Multi account PDF generation failed', [ 'statement_id' => $this->statement->id, @@ -139,6 +182,7 @@ class GenerateMultiAccountPdfJob implements ShouldQueue /** * Generate PDF untuk satu account + * Menggunakan data dari ProcessedStatement yang sudah diproses oleh ExportStatementPeriodJob * * @param Account $account * @return string|null Path to generated PDF @@ -146,10 +190,23 @@ class GenerateMultiAccountPdfJob implements ShouldQueue protected function generateAccountPdf($account) { try { - // Get statement entries - $stmtEntries = $this->getStatementEntries($account->account_number); + // Prepare account query untuk processing + $accountQuery = [ + 'account_number' => $account->account_number, + 'period' => $this->period + ]; - // Get saldo awal bulan + // Get total entry count + $totalCount = $this->getTotalEntryCount($account->account_number); + + // Delete existing processed data dan process ulang + $this->deleteExistingProcessedData($accountQuery); + $this->processAndSaveStatementEntries($account, $totalCount); + + // Get statement entries from ProcessedStatement (data yang sudah diproses) + $stmtEntries = $this->getProcessedStatementEntries($account->account_number); + + // Get saldo awal bulan menggunakan logika yang sama dengan ExportStatementPeriodJob $saldoAwalBulan = $this->getSaldoAwalBulan($account->account_number); // Get branch info @@ -157,17 +214,23 @@ class GenerateMultiAccountPdfJob implements ShouldQueue // Prepare images for PDF $images = $this->prepareImagesForPdf(); + + $headerImagePath = public_path('assets/media/images/bg-header-table.png'); + $headerTableBg = file_exists($headerImagePath) + ? base64_encode(file_get_contents($headerImagePath)) + : null; // Render HTML - $html = view('webstatement::statements.stmt', compact( - 'stmtEntries', - 'account', + $html = view('webstatement::statements.stmt', [ + 'stmtEntries' => $stmtEntries, + 'account' => $account, 'customer' => $account->customer, - 'images', - 'branch', + 'images' => $images, + 'branch' => $branch, 'period' => $this->period, - 'saldoAwalBulan' - ))->render(); + 'saldoAwalBulan' => $saldoAwalBulan, + 'headerTableBg' => $headerTableBg, + ])->render(); // Generate PDF filename $filename = "statement_{$account->account_number}_{$this->period}_" . now()->format('YmdHis') . '.pdf'; @@ -182,11 +245,12 @@ class GenerateMultiAccountPdfJob implements ShouldQueue // Generate PDF using Browsershot Browsershot::html($html) + ->showBackground() ->setOption('addStyleTag', json_encode(['content' => '@page { margin: 0; }'])) ->format('A4') ->margins(0, 0, 0, 0) ->waitUntilNetworkIdle() - ->timeout(60) + ->timeout(60000) ->save($pdfPath); // Verify file was created @@ -194,6 +258,9 @@ class GenerateMultiAccountPdfJob implements ShouldQueue throw new Exception('PDF file was not created'); } + // Clear variables to free memory + unset($html, $stmtEntries, $images); + return $pdfPath; } catch (Exception $e) { @@ -207,43 +274,342 @@ class GenerateMultiAccountPdfJob implements ShouldQueue } /** - * Get statement entries untuk account + * Get total entry count untuk account + * Menggunakan logika yang sama dengan ExportStatementPeriodJob + * + * @param string $accountNumber + * @return int + */ + protected function getTotalEntryCount($accountNumber): int + { + $query = StmtEntry::where('account_number', $accountNumber) + ->whereBetween('booking_date', [ + $this->startDate->format('Ymd'), + $this->endDate->format('Ymd') + ]); + + Log::info("Getting total entry count for PDF generation", [ + 'account' => $accountNumber, + 'start_date' => $this->startDate->format('Ymd'), + 'end_date' => $this->endDate->format('Ymd') + ]); + + return $query->count(); + } + + /** + * Delete existing processed data untuk account + * Menggunakan logika yang sama dengan ExportStatementPeriodJob + * + * @param array $criteria + * @return void + */ + protected function deleteExistingProcessedData(array $criteria): void + { + Log::info('Deleting existing processed data for PDF generation', [ + 'account_number' => $criteria['account_number'], + 'period' => $criteria['period'] + ]); + + ProcessedStatement::where('account_number', $criteria['account_number']) + ->where('period', $criteria['period']) + ->delete(); + } + + /** + * Process dan save statement entries untuk account + * Menggunakan logika yang sama dengan ExportStatementPeriodJob + * + * @param Account $account + * @param int $totalCount + * @return void + */ + protected function processAndSaveStatementEntries($account, int $totalCount): void + { + // Get saldo awal dari AccountBalance + $saldoAwalBulan = $this->getSaldoAwalBulan($account->account_number); + $runningBalance = (float) $saldoAwalBulan->actual_balance; + $globalSequence = 0; + + Log::info("Processing {$totalCount} statement entries for PDF generation", [ + 'account_number' => $account->account_number, + 'starting_balance' => $runningBalance + ]); + + StmtEntry::with(['ft', 'transaction']) + ->where('account_number', $account->account_number) + ->whereBetween('booking_date', [ + $this->startDate->format('Ymd'), + $this->endDate->format('Ymd') + ]) + ->orderBy('date_time', 'ASC') + ->orderBy('trans_reference', 'ASC') + ->chunk(1000, function ($entries) use (&$runningBalance, &$globalSequence, $account) { + $processedData = $this->prepareProcessedData($entries, $runningBalance, $globalSequence, $account->account_number); + + if (!empty($processedData)) { + DB::table('processed_statements')->insert($processedData); + } + }); + } + + /** + * Prepare processed data untuk batch insert + * Menggunakan logika yang sama dengan ExportStatementPeriodJob + * + * @param $entries + * @param float $runningBalance + * @param int $globalSequence + * @param string $accountNumber + * @return array + */ + protected function prepareProcessedData($entries, &$runningBalance, &$globalSequence, $accountNumber): array + { + $processedData = []; + + foreach ($entries as $item) { + $globalSequence++; + $runningBalance += (float) $item->amount_lcy; + + $actualDate = $this->formatActualDate($item); + + $processedData[] = [ + 'account_number' => $accountNumber, + 'period' => $this->period, + 'sequence_no' => $globalSequence, + 'transaction_date' => $item->booking_date, + 'reference_number' => $item->trans_reference, + 'transaction_amount' => $item->amount_lcy, + 'transaction_type' => $item->amount_lcy < 0 ? 'D' : 'C', + 'description' => $this->generateNarrative($item), + 'end_balance' => $runningBalance, + 'actual_date' => $actualDate, + 'created_at' => now(), + 'updated_at' => now(), + ]; + } + + return $processedData; + } + + /** + * Format actual date dari item + * Menggunakan logika yang sama dengan ExportStatementPeriodJob + * + * @param $item + * @return string + */ + protected function formatActualDate($item): string + { + try { + $prefix = substr($item->trans_reference ?? '', 0, 2); + $relationMap = [ + 'FT' => 'ft', + 'TT' => 'tt', + 'DC' => 'dc', + 'AA' => 'aa' + ]; + + $datetime = $item->date_time; + if (isset($relationMap[$prefix])) { + $relation = $relationMap[$prefix]; + $datetime = $item->$relation?->date_time ?? $datetime; + } + + return Carbon::createFromFormat( + 'ymdHi', + $datetime + )->format('d/m/Y H:i'); + } catch (Exception $e) { + Log::warning("Error formatting actual date: " . $e->getMessage()); + return Carbon::now()->format('d/m/Y H:i'); + } + } + + /** + * Generate narrative untuk statement entry + * Menggunakan logika yang sama dengan ExportStatementPeriodJob + * + * @param $item + * @return string + */ + protected function generateNarrative($item) + { + $narr = []; + + if ($item->transaction) { + if ($item->transaction->stmt_narr) { + $narr[] = $item->transaction->stmt_narr; + } + if ($item->narrative) { + $narr[] = $item->narrative; + } + if ($item->transaction->narr_type) { + $narr[] = $this->getFormatNarrative($item->transaction->narr_type, $item); + } + } else if ($item->narrative) { + $narr[] = $item->narrative; + } + + if ($item->ft?->recipt_no) { + $narr[] = 'Receipt No: ' . $item->ft->recipt_no; + } + + return implode(' ', array_filter($narr)); + } + + /** + * Get formatted narrative berdasarkan narrative type + * Menggunakan logika yang sama dengan ExportStatementPeriodJob + * + * @param $narr + * @param $item + * @return string + */ + protected function getFormatNarrative($narr, $item) + { + + $narrParam = TempStmtNarrParam::where('_id', $narr)->first(); + + if (!$narrParam) { + return ''; + } + + $fmt = ''; + if ($narrParam->_id == 'FTIN') { + $fmt = 'FT.IN'; + } else if ($narrParam->_id == 'FTOUT') { + $fmt = 'FT.OUT'; + } else if ($narrParam->_id == 'TTTRFOUT') { + $fmt = 'TT.O.TRF'; + } else if ($narrParam->_id == 'TTTRFIN') { + $fmt = 'TT.I.TRF'; + } else if ($narrParam->_id == 'APITRX'){ + $fmt = 'API.TSEL'; + } else if ($narrParam->_id == 'ONUSCR'){ + $fmt = 'ONUS.CR'; + } else if ($narrParam->_id == 'ONUSDR'){ + $fmt = 'ONUS.DR'; + }else { + $fmt = $narrParam->_id; + } + + $narrFormat = TempStmtNarrFormat::where('_id', $fmt)->first(); + + if (!$narrFormat) { + return ''; + } + + // Get the format string from the database + $formatString = $narrFormat->text_data ?? ''; + + // Parse the format string + // Split by the separator ']' + $parts = explode(']', $formatString); + + $result = ''; + + foreach ($parts as $index => $part) { + if (empty($part)) { + continue; + } + + if ($index === 0) { + // For the first part, take only what's before the '!' + $splitPart = explode('!', $part); + if (count($splitPart) > 0) { + // Remove quotes, backslashes, and other escape characters + $cleanPart = trim($splitPart[0]).' '; + // Remove quotes at the beginning and end + $cleanPart = preg_replace('/^["\'\\\\]+|["\'\\\\]+$/', '', $cleanPart); + // Remove any remaining backslashes + $cleanPart = str_replace('\\', '', $cleanPart); + // Remove any remaining quotes + $cleanPart = str_replace('"', '', $cleanPart); + $result .= $cleanPart; + } + } else { + // For other parts, these are field placeholders + $fieldName = strtolower(str_replace('.', '_', $part)); + + // Get the corresponding parameter value from narrParam + $paramValue = null; + + // Check if the field exists as a property in narrParam + if (property_exists($narrParam, $fieldName)) { + $paramValue = $narrParam->$fieldName; + } else if (isset($narrParam->$fieldName)) { + $paramValue = $narrParam->$fieldName; + } + + // If we found a value, add it to the result + if ($paramValue !== null) { + $result .= $paramValue; + } else { + // If no value found, try to use the original field name as a fallback + if ($fieldName !== 'recipt_no') { + $prefix = substr($item->trans_reference ?? '', 0, 2); + $relationMap = [ + 'FT' => 'ft', + 'TT' => 'tt', + 'DC' => 'dc', + 'AA' => 'aa' + ]; + + if (isset($relationMap[$prefix])) { + $relation = $relationMap[$prefix]; + $result .= ($item->$relation?->$fieldName ?? '') . ' '; + } + } + } + } + } + + return str_replace('', ' ', $result); + } + + /** + * Get processed statement entries untuk account + * Menggunakan data dari tabel ProcessedStatement yang sudah diproses * * @param string $accountNumber * @return \Illuminate\Database\Eloquent\Collection */ - protected function getStatementEntries($accountNumber) + protected function getProcessedStatementEntries($accountNumber) { - // Calculate period dates - $year = substr($this->period, 0, 4); - $month = substr($this->period, 4, 2); + Log::info('Getting processed statement entries', [ + 'account_number' => $accountNumber, + 'period' => $this->period + ]); - if ($this->period === '202505') { - $startDate = Carbon::createFromDate($year, $month, 12)->startOfDay(); - } else { - $startDate = Carbon::createFromDate($year, $month, 1)->startOfDay(); - } - - $endDate = Carbon::createFromDate($year, $month, 1)->endOfMonth()->endOfDay(); - - return StmtEntry::where('account_number', $accountNumber) - ->whereBetween('booking_date', [ - $startDate->format('Ymd'), - $endDate->format('Ymd') - ]) - ->orderBy('date_time', 'ASC') - ->orderBy('trans_reference', 'ASC') + return ProcessedStatement::where('account_number', $accountNumber) + ->where('period', $this->period) + ->orderBy('sequence_no', 'ASC') ->get(); } /** * Get saldo awal bulan untuk account + * Menggunakan logika yang sama dengan ExportStatementPeriodJob * * @param string $accountNumber * @return object */ protected function getSaldoAwalBulan($accountNumber) { + // Menggunakan logika yang sama dengan ExportStatementPeriodJob + // Ambil saldo dari ProcessedStatement entry pertama dikurangi transaction_amount + $firstEntry = ProcessedStatement::where('account_number', $accountNumber) + ->where('period', $this->period) + ->orderBy('sequence_no', 'ASC') + ->first(); + + if ($firstEntry) { + $saldoAwal = $firstEntry->end_balance - $firstEntry->transaction_amount; + return (object) ['actual_balance' => $saldoAwal]; + } + + // Fallback ke AccountBalance jika tidak ada ProcessedStatement $saldoPeriod = $this->calculateSaldoPeriod($this->period); $saldo = AccountBalance::where('account_number', $accountNumber) @@ -255,6 +621,7 @@ class GenerateMultiAccountPdfJob implements ShouldQueue /** * Calculate saldo period berdasarkan aturan bisnis + * Menggunakan logika yang sama dengan ExportStatementPeriodJob * * @param string $period * @return string @@ -299,6 +666,7 @@ class GenerateMultiAccountPdfJob implements ShouldQueue $images[$key] = base64_encode(file_get_contents($fullPath)); } else { $images[$key] = null; + Log::warning('Image file not found', ['path' => $fullPath]); } } @@ -348,6 +716,13 @@ class GenerateMultiAccountPdfJob implements ShouldQueue 'statement_id' => $this->statement->id ]); + // Clean up individual PDF files after creating ZIP + foreach ($pdfFiles as $pdfFile) { + if (file_exists($pdfFile)) { + unlink($pdfFile); + } + } + return $zipPath; } catch (Exception $e) { diff --git a/resources/views/statements/index.blade.php b/resources/views/statements/index.blade.php index 9e7d586..5c46646 100644 --- a/resources/views/statements/index.blade.php +++ b/resources/views/statements/index.blade.php @@ -350,6 +350,12 @@ }, account_number: { title: 'Account Number', + render: (item, data) => { + if(data.request_type=="multi_account"){ + return data.stmt_sent_type ?? 'N/A'; + } + return data.account_number ?? ''; + }, }, period: { title: 'Period',