diff --git a/app/Http/Controllers/PrintStatementController.php b/app/Http/Controllers/PrintStatementController.php index 006c740..4722829 100644 --- a/app/Http/Controllers/PrintStatementController.php +++ b/app/Http/Controllers/PrintStatementController.php @@ -6,20 +6,17 @@ use App\Http\Controllers\Controller; use Carbon\Carbon; use Exception; use Illuminate\Http\Request; -use Illuminate\Validation\Rule; use Illuminate\Support\Facades\{Auth, DB, Log, Mail, Storage}; +use Illuminate\Validation\Rule; use Modules\Basicdata\Models\Branch; -use Modules\Webstatement\{ - Http\Requests\PrintStatementRequest, - Mail\StatementEmail, - Models\PrintStatementLog, - Models\Account, - Models\AccountBalance, - Jobs\ExportStatementPeriodJob -}; -use Modules\Webstatement\Models\ProcessedStatement; -use ZipArchive; +use Modules\Webstatement\Http\Requests\PrintStatementRequest; +use Modules\Webstatement\Jobs\{ExportStatementPeriodJob, GenerateMultiAccountPdfJob}; +use Modules\Webstatement\Mail\StatementEmail; +use Modules\Webstatement\Models\{Account, AccountBalance, PrintStatementLog, ProcessedStatement}; use Spatie\Browsershot\Browsershot; +use ZipArchive; +ini_set('memory_limit', '2G'); // Atau '1G' untuk data yang sangat besar +ini_set('max_execution_time', 300000); class PrintStatementController extends Controller { @@ -83,7 +80,7 @@ use Spatie\Browsershot\Browsershot; $validated = $request->validated(); $validated['request_type'] = 'single_account'; // Default untuk request manual - if($validated['branch_code'] && $validated['stmt_sent_type']){ + if($validated['branch_code'] && !empty($validated['stmt_sent_type'])){ $validated['request_type'] = 'multi_account'; // Default untuk request manual } @@ -99,9 +96,9 @@ use Spatie\Browsershot\Browsershot; $validated['processed_accounts'] = 0; $validated['success_count'] = 0; $validated['failed_count'] = 0; - $validated['stmt_sent_type'] = $request->input('stmt_sent_type') ? implode(',', $request->input('stmt_sent_type')) : ''; + $validated['stmt_sent_type'] = $request->input('stmt_sent_type') ? json_encode($request->input('stmt_sent_type')) : ''; $validated['branch_code'] = $validated['branch_code'] ?? $branch_code; // Awal tidak tersedia - + // Create the statement log $statement = PrintStatementLog::create($validated); @@ -942,7 +939,7 @@ use Spatie\Browsershot\Browsershot; ->format('A4') ->margins(0, 0, 0, 0) ->waitUntilNetworkIdle() - ->timeout(60) + ->timeout(6000) ->save($tempPath); // Verifikasi file berhasil dibuat @@ -1275,7 +1272,157 @@ use Spatie\Browsershot\Browsershot; } } + /** + * Process statement untuk multi account berdasarkan stmt_sent_type + * + * @param PrintStatementLog $statement + * @return \Illuminate\Http\JsonResponse + */ function printStatementRekening($statement) { + + try { + // DB::beginTransaction(); + + Log::info('Starting statement processing', [ + 'statement_id' => $statement->id, + 'request_type' => $statement->request_type, + 'stmt_sent_type' => $statement->stmt_sent_type, + 'branch_code' => $statement->branch_code + ]); + + if ($statement->request_type === 'multi_account') { + return $this->processMultiAccountStatement($statement); + } else { + return $this->processSingleAccountStatement($statement); + } + + } catch (\Exception $e) { + //DB::rollBack(); + + Log::error('Failed to process statement', [ + 'error' => $e->getMessage(), + 'statement_id' => $statement->id, + 'trace' => $e->getTraceAsString() + ]); + + return response()->json([ + 'success' => false, + 'message' => 'Failed to process statement', + 'error' => $e->getMessage() + ], 500); + } + } + + /** + * Process multi account statement berdasarkan stmt_sent_type + * + * @param PrintStatementLog $statement + * @return \Illuminate\Http\JsonResponse + */ + protected function processMultiAccountStatement($statement) + { + try { + $period = $statement->period_from ?? date('Ym'); + $clientName = 'client1'; + + // Validasi stmt_sent_type + if (empty($statement->stmt_sent_type)) { + throw new \Exception('stmt_sent_type is required for multi account processing'); + } + + // 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]; + } + + Log::info('Processing multi account statement', [ + 'statement_id' => $statement->id, + 'branch_code' => $statement->branch_code, + 'stmt_sent_types' => $stmtSentTypes, + 'period' => $period + ]); + + // Ambil accounts berdasarkan branch_code dan stmt_sent_type + $accounts = Account::where('branch_code', $statement->branch_code) + ->whereIn('stmt_sent_type', $stmtSentTypes) + ->with('customer') + ->get(); + + if ($accounts->isEmpty()) { + throw new \Exception('No accounts found for the specified criteria'); + } + + Log::info('Found accounts for processing', [ + 'total_accounts' => $accounts->count(), + 'branch_code' => $statement->branch_code, + 'stmt_sent_types' => $stmtSentTypes + ]); + + // Update statement log dengan informasi accounts + $accountNumbers = $accounts->pluck('account_number')->toArray(); + $statement->update([ + 'target_accounts' => $accountNumbers, + 'total_accounts' => $accounts->count(), + 'status' => 'processing', + 'started_at' => now() + ]); + + // Dispatch job untuk generate PDF multi account + $job = GenerateMultiAccountPdfJob::dispatch( + $statement, + $accounts, + $period, + $clientName + ); + + DB::commit(); + + Log::info('Multi account PDF generation job dispatched', [ + 'job_id' => $job->job_id ?? null, + 'statement_id' => $statement->id, + 'total_accounts' => $accounts->count(), + 'period' => $period + ]); + + return response()->json([ + 'success' => true, + 'message' => 'Multi account statement processing queued successfully', + 'data' => [ + 'job_id' => $job->job_id ?? null, + 'statement_id' => $statement->id, + 'total_accounts' => $accounts->count(), + 'account_numbers' => $accountNumbers, + 'period' => $period, + 'client_name' => $clientName + ] + ]); + + } catch (\Exception $e) { + DB::rollBack(); + + Log::error('Failed to process multi account statement', [ + 'error' => $e->getMessage(), + 'statement_id' => $statement->id, + 'trace' => $e->getTraceAsString() + ]); + + throw $e; + } + } + + /** + * Process single account statement (existing logic) + * + * @param PrintStatementLog $statement + * @return \Illuminate\Http\JsonResponse + */ + protected function processSingleAccountStatement($statement) + { + $accountNumber = $statement->account_number; $period = $statement->period_from ?? date('Ym'); $balance = AccountBalance::where('account_number', $accountNumber) @@ -1301,7 +1448,7 @@ use Spatie\Browsershot\Browsershot; } // Dispatch the job - $job = ExportStatementPeriodJob::dispatch($statement, $accountNumber, $period, $balance, $clientName); + $job = ExportStatementPeriodJob::dispatch($statement->id, $accountNumber, $period, $balance, $clientName); Log::info("Statement export job dispatched successfully", [ 'job_id' => $job->job_id ?? null, @@ -1335,4 +1482,80 @@ use Spatie\Browsershot\Browsershot; ]); } } + + /** + * Download ZIP file untuk multi account statement + * + * @param int $statementId + * @return \Illuminate\Http\Response + */ + public function downloadMultiAccountZip($statementId) + { + try { + $statement = PrintStatementLog::findOrFail($statementId); + + if ($statement->request_type !== 'multi_account') { + return response()->json([ + 'success' => false, + 'message' => 'This statement is not a multi account request' + ], 400); + } + + if (!$statement->is_available) { + return response()->json([ + 'success' => false, + 'message' => 'Statement files are not available for download' + ], 404); + } + + // Find ZIP file + $zipFiles = Storage::disk('local')->files("statements/{$statement->period_from}/multi_account/{$statementId}"); + + $zipFile = null; + foreach ($zipFiles as $file) { + if (pathinfo($file, PATHINFO_EXTENSION) === 'zip') { + $zipFile = $file; + break; + } + } + + if (!$zipFile || !Storage::disk('local')->exists($zipFile)) { + return response()->json([ + 'success' => false, + 'message' => 'ZIP file not found' + ], 404); + } + + $zipPath = Storage::disk('local')->path($zipFile); + $filename = basename($zipFile); + + // Update download status + $statement->update([ + 'is_downloaded' => true, + 'downloaded_at' => now() + ]); + + Log::info('Multi account ZIP downloaded', [ + 'statement_id' => $statementId, + 'zip_file' => $zipFile, + 'user_id' => auth()->id() + ]); + + return response()->download($zipPath, $filename, [ + 'Content-Type' => 'application/zip' + ]); + + } catch (Exception $e) { + Log::error('Failed to download multi account ZIP', [ + 'statement_id' => $statementId, + 'error' => $e->getMessage() + ]); + + return response()->json([ + 'success' => false, + 'message' => 'Failed to download ZIP file', + 'error' => $e->getMessage() + ], 500); + } + } } diff --git a/app/Jobs/ExportStatementPeriodJob.php b/app/Jobs/ExportStatementPeriodJob.php index 49b1667..c2a5261 100644 --- a/app/Jobs/ExportStatementPeriodJob.php +++ b/app/Jobs/ExportStatementPeriodJob.php @@ -33,7 +33,7 @@ class ExportStatementPeriodJob implements ShouldQueue protected $startDate; protected $endDate; protected $toCsv; - protected $statement; + protected $statementId; /** * Create a new job instance. @@ -44,9 +44,9 @@ class ExportStatementPeriodJob implements ShouldQueue * @param string $client * @param string $disk */ - public function __construct($statement, string $account_number, string $period, string $saldo, string $client = '', string $disk = 'local', bool $toCsv = true) + public function __construct(int $statementId, string $account_number, string $period, string $saldo, string $client = '', string $disk = 'local', bool $toCsv = true) { - $this->statement = $statement; + $this->statementId = $statementId; $this->account_number = $account_number; $this->period = $period; $this->saldo = $saldo; @@ -172,7 +172,7 @@ class ExportStatementPeriodJob implements ShouldQueue }); if($entry){ - $printLog = PrintStatementLog::find($this->statement->id); + $printLog = PrintStatementLog::find($this->statementId); if($printLog){ $printLog->update(['is_generated' => true]); } diff --git a/app/Jobs/GenerateMultiAccountPdfJob.php b/app/Jobs/GenerateMultiAccountPdfJob.php new file mode 100644 index 0000000..c4c9647 --- /dev/null +++ b/app/Jobs/GenerateMultiAccountPdfJob.php @@ -0,0 +1,362 @@ +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; + } + } +} \ No newline at end of file diff --git a/resources/views/statements/stmt.blade.php b/resources/views/statements/stmt.blade.php index 640e88b..e7f30c6 100644 --- a/resources/views/statements/stmt.blade.php +++ b/resources/views/statements/stmt.blade.php @@ -310,8 +310,8 @@ // Add 1 for the "Saldo Awal Bulan" row $totalLines += 1; - // Calculate total pages (19 lines per page) - $totalPages = ceil($totalLines / 19); + // Calculate total pages (18 lines per page) + $totalPages = ceil($totalLines / 18); $pageNumber = 0; $footerContent = @@ -449,7 +449,7 @@ - @if ($line >= 19 && !$loop->last) + @if ($line >= 18 && !$loop->last) @php $line = 0; $pageNumber++; @@ -520,7 +520,7 @@ @endif @endforeach - @for ($i = 0; $i < 19 - $line; $i++) + @for ($i = 0; $i < 18 - $line; $i++) diff --git a/routes/web.php b/routes/web.php index f150339..b9800fd 100644 --- a/routes/web.php +++ b/routes/web.php @@ -116,8 +116,19 @@ Route::get('migrasi', [MigrasiController::class, 'index'])->name('migrasi.index' Route::get('biaya-kartu', [SyncLogsController::class, 'index'])->name('biaya-kartu.index'); Route::get('/stmt-entries/{accountNumber}', [MigrasiController::class, 'getStmtEntryByAccount']); +Route::get('/stmt-entries/{accountNumber}', [PrintStatementController::class, 'generated']); Route::get('/stmt-export-csv', [WebstatementController::class, 'index'])->name('webstatement.index'); +// Route untuk generate PDF +Route::get('/statements/{norek}/pdf/{period?}', [PrintStatementController::class, 'generated']) + ->defaults('format', 'pdf') + ->name('statements.pdf'); + +// Route untuk preview PDF +Route::get('/statements/{norek}/preview/{period?}', [PrintStatementController::class, 'previewPdf']) + ->name('statements.preview'); + + Route::prefix('debug')->group(function () { Route::get('/test-statement',[WebstatementController::class,'printStatementRekening'])->name('webstatement.test');