diff --git a/app/Jobs/GenerateClosingBalanceReportJob.php b/app/Jobs/GenerateClosingBalanceReportJob.php index 77ed5a3..5f355c9 100644 --- a/app/Jobs/GenerateClosingBalanceReportJob.php +++ b/app/Jobs/GenerateClosingBalanceReportJob.php @@ -44,8 +44,7 @@ /** * Execute the job dengan optimasi performa */ - public function handle() - : void + public function handle(): void { $reportLog = ClosingBalanceReportLog::find($this->reportLogId); @@ -62,44 +61,41 @@ 'report_log_id' => $this->reportLogId ]); - DB::beginTransaction(); - // Update status to processing $reportLog->update([ 'status' => 'processing', 'updated_at' => now() ]); - // Step 1: Process and save to database (fast) - $this->processAndSaveClosingBalanceData(); + // Gunakan satu transaksi untuk seluruh proses + DB::transaction(function () use ($reportLog) { + // Step 1: Process and save to database (fast) + $this->processAndSaveClosingBalanceData(); - // Step 2: Export from database to CSV (fast) - $filePath = $this->exportFromDatabaseToCsv(); + // Step 2: Export from database to CSV (fast) + $filePath = $this->exportFromDatabaseToCsv(); - // Get record count from database - $recordCount = $this->getProcessedRecordCount(); + // Get record count from database + $recordCount = $this->getProcessedRecordCount(); - // Update report log with success - $reportLog->update([ - 'status' => 'completed', - 'file_path' => $filePath, - 'file_size' => Storage::disk($this->disk)->size($filePath), - 'record_count' => $recordCount, - 'updated_at' => now() - ]); + // Update report log with success + $reportLog->update([ + 'status' => 'completed', + 'file_path' => $filePath, + 'file_size' => Storage::disk($this->disk)->size($filePath), + 'record_count' => $recordCount, + 'updated_at' => now() + ]); - DB::commit(); - - Log::info('Optimized closing balance report generation completed successfully', [ - 'account_number' => $this->accountNumber, - 'period' => $this->period, - 'file_path' => $filePath, - 'record_count' => $recordCount - ]); + Log::info('Optimized closing balance report generation completed successfully', [ + 'account_number' => $this->accountNumber, + 'period' => $this->period, + 'file_path' => $filePath, + 'record_count' => $recordCount + ]); + }); } catch (Exception $e) { - DB::rollback(); - Log::error('Error generating optimized closing balance report', [ 'account_number' => $this->accountNumber, 'period' => $this->period, @@ -121,8 +117,7 @@ * Process and save closing balance data to database dengan proteksi duplikasi * Memproses dan menyimpan data closing balance dengan perlindungan terhadap duplikasi */ - private function processAndSaveClosingBalanceData() - : void + private function processAndSaveClosingBalanceData(): void { $criteria = [ 'account_number' => $this->accountNumber, @@ -130,62 +125,41 @@ 'group_name' => $this->groupName ]; - DB::beginTransaction(); + // HAPUS DB::beginTransaction() - sudah ada di handle() - try { - // Sederhana: hapus data existing terlebih dahulu seperti ExportStatementJob - $this->deleteExistingProcessedData($criteria); + // Sederhana: hapus data existing terlebih dahulu seperti ExportStatementJob + $this->deleteExistingProcessedData($criteria); - // Get opening balance - $runningBalance = $this->getOpeningBalance(); - $sequenceNo = 0; + // Get opening balance + $runningBalance = $this->getOpeningBalance(); + $sequenceNo = 0; - Log::info('Starting to process closing balance data', [ - 'opening_balance' => $runningBalance, - 'criteria' => $criteria - ]); + Log::info('Starting to process closing balance data', [ + 'opening_balance' => $runningBalance, + 'criteria' => $criteria + ]); - // Build query yang sederhana tanpa eliminasi duplicate rumit - $query = $this->buildTransactionQuery(); + // Build query yang sederhana tanpa eliminasi duplicate rumit + $query = $this->buildTransactionQuery(); - // Proses dan insert data langsung seperti ExportStatementJob - $query->chunk($this->chunkSize, function ($transactions) use (&$runningBalance, &$sequenceNo) { - $processedData = $this->prepareProcessedClosingBalanceData($transactions, $runningBalance, $sequenceNo); + // Proses dan insert data dengan batch updateOrCreate untuk efisiensi + $query->chunk($this->chunkSize, function ($transactions) use (&$runningBalance, &$sequenceNo) { + $processedData = $this->prepareProcessedClosingBalanceData($transactions, $runningBalance, $sequenceNo); - if (!empty($processedData)) { - foreach ($processedData as $data) { - ProcessedClosingBalance::updateOrCreate( - [ - 'account_number' => $data['account_number'], - 'period' => $data['period'], - 'trans_reference' => $data['trans_reference'], - 'amount_lcy' => $data['amount_lcy'], - ], - $data - ); - } - } - }); + if (!empty($processedData)) { + // Gunakan batch processing untuk updateOrCreate + $this->batchUpdateOrCreate($processedData); + } + }); - DB::commit(); + // HAPUS DB::commit() - akan di-handle di handle() - $recordCount = $this->getProcessedRecordCount(); - Log::info('Closing balance data processing completed successfully', [ - 'final_sequence' => $sequenceNo, - 'final_balance' => $runningBalance, - 'record_count' => $recordCount - ]); - - } catch (Exception $e) { - DB::rollback(); - - Log::error('Error in processAndSaveClosingBalanceData', [ - 'error' => $e->getMessage(), - 'criteria' => $criteria - ]); - - throw $e; - } + $recordCount = $this->getProcessedRecordCount(); + Log::info('Closing balance data processing completed successfully', [ + 'final_sequence' => $sequenceNo, + 'final_balance' => $runningBalance, + 'record_count' => $recordCount + ]); } /** @@ -601,4 +575,67 @@ 'criteria' => $criteria ]); } + + /** + * Batch update or create untuk mengurangi jumlah query dan lock + * Menggunakan pendekatan yang lebih efisien untuk menghindari max_lock_per_transaction + */ + private function batchUpdateOrCreate(array $processedData): void + { + Log::info('Starting batch updateOrCreate', [ + 'batch_size' => count($processedData) + ]); + + // Kumpulkan semua trans_reference yang akan diproses + $transReferences = collect($processedData)->pluck('trans_reference')->toArray(); + + // Ambil data yang sudah ada dalam satu query + $existingRecords = ProcessedClosingBalance::where('account_number', $this->accountNumber) + ->where('period', $this->period) + ->where('group_name', $this->groupName) + ->whereIn('trans_reference', $transReferences) + ->get() + ->keyBy(function ($item) { + return $item->trans_reference . '_' . $item->amount_lcy; + }); + + $toInsert = []; + $toUpdate = []; + + foreach ($processedData as $data) { + $key = $data['trans_reference'] . '_' . $data['amount_lcy']; + + if ($existingRecords->has($key)) { + // Record sudah ada, siapkan untuk update + $existingRecord = $existingRecords->get($key); + $toUpdate[] = [ + 'id' => $existingRecord->id, + 'data' => $data + ]; + } else { + // Record baru, siapkan untuk insert + $toInsert[] = $data; + } + } + + // Batch insert untuk record baru + if (!empty($toInsert)) { + DB::table('processed_closing_balances')->insert($toInsert); + Log::info('Batch insert completed', ['count' => count($toInsert)]); + } + + // Batch update untuk record yang sudah ada + if (!empty($toUpdate)) { + foreach ($toUpdate as $updateItem) { + ProcessedClosingBalance::where('id', $updateItem['id']) + ->update($updateItem['data']); + } + Log::info('Batch update completed', ['count' => count($toUpdate)]); + } + + Log::info('Batch updateOrCreate completed successfully', [ + 'inserted' => count($toInsert), + 'updated' => count($toUpdate) + ]); + } }