statement = $statement; $this->accounts = $accounts; $this->period = $period; $this->clientName = $clientName; } /** * Execute the job. */ 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 ]); $pdfFiles = []; $successCount = 0; $failedCount = 0; $errors = []; // Process each account foreach ($this->accounts as $account) { try { $pdfPath = $this->generateAccountPdf($account); if ($pdfPath) { $pdfFiles[] = $pdfPath; $successCount++; Log::info('PDF generated successfully for account', [ 'account_number' => $account->account_number, 'pdf_path' => $pdfPath ]); } } catch (Exception $e) { $failedCount++; $errors[] = [ 'account_number' => $account->account_number, 'error' => $e->getMessage() ]; Log::error('Failed to generate PDF for account', [ 'account_number' => $account->account_number, 'error' => $e->getMessage() ]); } } // Create ZIP file if there are PDFs $zipPath = null; if (!empty($pdfFiles)) { $zipPath = $this->createZipFile($pdfFiles); } // Update statement log $this->statement->update([ 'processed_accounts' => $this->accounts->count(), 'success_count' => $successCount, 'failed_count' => $failedCount, 'status' => $failedCount > 0 ? 'completed_with_errors' : 'completed', 'completed_at' => now(), 'is_available' => $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, 'failed_count' => $failedCount, 'zip_path' => $zipPath ]); } catch (Exception $e) { DB::rollBack(); Log::error('Multi account PDF generation failed', [ 'statement_id' => $this->statement->id, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); // Update statement with error status $this->statement->update([ 'status' => 'failed', 'completed_at' => now(), 'error_message' => $e->getMessage() ]); throw $e; } } /** * Generate PDF untuk satu account * * @param Account $account * @return string|null Path to generated PDF */ protected function generateAccountPdf($account) { try { // Get statement entries $stmtEntries = $this->getStatementEntries($account->account_number); // Get saldo awal bulan $saldoAwalBulan = $this->getSaldoAwalBulan($account->account_number); // Get branch info $branch = Branch::where('code', $account->branch_code)->first(); // Prepare images for PDF $images = $this->prepareImagesForPdf(); // Render HTML $html = view('webstatement::statements.stmt', compact( 'stmtEntries', 'account', 'customer' => $account->customer, 'images', 'branch', 'period' => $this->period, 'saldoAwalBulan' ))->render(); // Generate PDF filename $filename = "statement_{$account->account_number}_{$this->period}_" . now()->format('YmdHis') . '.pdf'; $storagePath = "statements/{$this->period}/multi_account/{$this->statement->id}"; $fullStoragePath = "{$storagePath}/{$filename}"; // Ensure directory exists Storage::disk('local')->makeDirectory($storagePath); // Generate PDF path $pdfPath = storage_path("app/{$fullStoragePath}"); // Generate PDF using Browsershot Browsershot::html($html) ->setOption('addStyleTag', json_encode(['content' => '@page { margin: 0; }'])) ->format('A4') ->margins(0, 0, 0, 0) ->waitUntilNetworkIdle() ->timeout(60) ->save($pdfPath); // Verify file was created if (!file_exists($pdfPath)) { throw new Exception('PDF file was not created'); } return $pdfPath; } catch (Exception $e) { Log::error('Failed to generate PDF for account', [ 'account_number' => $account->account_number, 'error' => $e->getMessage() ]); throw $e; } } /** * Get statement entries untuk account * * @param string $accountNumber * @return \Illuminate\Database\Eloquent\Collection */ protected function getStatementEntries($accountNumber) { // Calculate period dates $year = substr($this->period, 0, 4); $month = substr($this->period, 4, 2); 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') ->get(); } /** * Get saldo awal bulan untuk account * * @param string $accountNumber * @return object */ protected function getSaldoAwalBulan($accountNumber) { $saldoPeriod = $this->calculateSaldoPeriod($this->period); $saldo = AccountBalance::where('account_number', $accountNumber) ->where('period', $saldoPeriod) ->first(); return $saldo ?: (object) ['actual_balance' => 0]; } /** * Calculate saldo period berdasarkan aturan bisnis * * @param string $period * @return string */ protected function calculateSaldoPeriod($period) { if ($period === '202505') { return '20250510'; } // For periods after 202505, get last day of previous month if ($period > '202505') { $year = substr($period, 0, 4); $month = substr($period, 4, 2); $firstDay = Carbon::createFromFormat('Ym', $period)->startOfMonth(); return $firstDay->copy()->subDay()->format('Ymd'); } return $period . '01'; } /** * Prepare images as base64 for PDF * * @return array */ protected function prepareImagesForPdf() { $images = []; $imagePaths = [ 'headerTableBg' => 'assets/media/images/bg-header-table.png', 'watermark' => 'assets/media/images/watermark.png', 'logoArthagraha' => 'assets/media/images/logo-arthagraha.png', 'logoAgi' => 'assets/media/images/logo-agi.png', 'bannerFooter' => 'assets/media/images/banner-footer.png' ]; foreach ($imagePaths as $key => $path) { $fullPath = public_path($path); if (file_exists($fullPath)) { $images[$key] = base64_encode(file_get_contents($fullPath)); } else { $images[$key] = null; } } return $images; } /** * Create ZIP file dari multiple PDF files * * @param array $pdfFiles * @return string|null Path to ZIP file */ protected function createZipFile($pdfFiles) { try { $zipFilename = "statements_{$this->period}_multi_account_{$this->statement->id}_" . now()->format('YmdHis') . '.zip'; $zipStoragePath = "statements/{$this->period}/multi_account/{$this->statement->id}"; $fullZipPath = "{$zipStoragePath}/{$zipFilename}"; // Ensure directory exists Storage::disk('local')->makeDirectory($zipStoragePath); $zipPath = storage_path("app/{$fullZipPath}"); $zip = new ZipArchive(); if ($zip->open($zipPath, ZipArchive::CREATE) !== TRUE) { throw new Exception('Cannot create ZIP file'); } foreach ($pdfFiles as $pdfFile) { if (file_exists($pdfFile)) { $filename = basename($pdfFile); $zip->addFile($pdfFile, $filename); } } $zip->close(); // Verify ZIP file was created if (!file_exists($zipPath)) { throw new Exception('ZIP file was not created'); } Log::info('ZIP file created successfully', [ 'zip_path' => $zipPath, 'pdf_count' => count($pdfFiles), 'statement_id' => $this->statement->id ]); return $zipPath; } catch (Exception $e) { Log::error('Failed to create ZIP file', [ 'error' => $e->getMessage(), 'statement_id' => $this->statement->id ]); return null; } } }