diff --git a/app/Jobs/GenerateAtmTransactionReportJob.php b/app/Jobs/GenerateAtmTransactionReportJob.php new file mode 100644 index 0000000..d712e75 --- /dev/null +++ b/app/Jobs/GenerateAtmTransactionReportJob.php @@ -0,0 +1,156 @@ +period = $period; + $this->reportLogId = $reportLogId; + } + + public function handle(): void + { + $reportLog = null; + if ($this->reportLogId) { + $reportLog = AtmTransactionReportLog::find($this->reportLogId); + } + + try { + Log::info("Starting ATM Transaction report generation for period: {$this->period} and LogId: {$this->reportLogId}"); + + $filename = "atm_transaction_report_{$this->period}.csv"; + $filePath = "reports/atm_transactions/{$filename}"; + + // Create directory if not exists + Storage::makeDirectory('reports/atm_transactions'); + + // Initialize CSV file with headers + $headers = [ + 'reff_no', + 'pan', + 'atm_id_terminal_id', + 'amount', + 'channel', + 'account_no', + 'internal_account', + 'transaction_type', + 'trans_ref', + 'posting_date', + 'stan', + 'trans_status' + ]; + + $csvContent = implode(self::CSV_DELIMITER, $headers) . "\n"; + Storage::put($filePath, $csvContent); + + $totalRecords = 0; + + // Process data in chunks + AtmTransaction::select( + 'retrieval_ref_no as reff_no', + 'pan_number as pan', + 'card_acc_id as atm_id_terminal_id', + 'txn_amount as amount', + 'merchant_id as channel', + 'debit_acct_no as account_no', + 'credit_acct_no as internal_account', + 'txn_type as transaction_type', + 'trans_ref', + 'booking_date as posting_date', + 'stan_no as stan', + 'trans_status' + ) + ->where('booking_date', $this->period) + ->chunk(self::CHUNK_SIZE, function ($transactions) use ($filePath, &$totalRecords) { + $csvRows = []; + + foreach ($transactions as $transaction) { + $csvRows[] = implode(self::CSV_DELIMITER, [ + $this->escapeCsvValue($transaction->reff_no), + $this->escapeCsvValue($transaction->pan), + $this->escapeCsvValue($transaction->atm_id_terminal_id), + $this->escapeCsvValue($transaction->amount), + $this->escapeCsvValue($transaction->channel), + $this->escapeCsvValue($transaction->account_no), + $this->escapeCsvValue($transaction->internal_account), + $this->escapeCsvValue($transaction->transaction_type), + $this->escapeCsvValue($transaction->trans_ref), + $this->escapeCsvValue($transaction->posting_date), + $this->escapeCsvValue($transaction->stan), + $this->escapeCsvValue($transaction->trans_status) + ]); + $totalRecords++; + } + + if (!empty($csvRows)) { + Storage::append($filePath, implode("\n", $csvRows)); + } + }); + + // Update report log if exists + if ($reportLog) { + $reportLog->update([ + 'status' => 'completed', + 'file_path' => $filePath, + 'file_size' => Storage::size($filePath), + 'record_count' => $totalRecords, + ]); + } + + Log::info("ATM Transaction report generated successfully. File: {$filePath}, Total records: {$totalRecords}"); + + } catch (Exception $e) { + if ($reportLog) { + $reportLog->update([ + 'status' => 'failed', + 'error_message' => $e->getMessage(), + ]); + } + + Log::error("Error generating ATM Transaction report for period {$this->period}: " . $e->getMessage()); + throw $e; + } + } + + /** + * Escape CSV values to handle commas and quotes + */ + private function escapeCsvValue($value): string + { + if ($value === null) { + return ''; + } + + $value = (string) $value; + + // If value contains comma, quote, or newline, wrap in quotes and escape internal quotes + if (strpos($value, self::CSV_DELIMITER) !== false || + strpos($value, '"') !== false || + strpos($value, "\n") !== false) { + $value = '"' . str_replace('"', '""', $value) . '"'; + } + + return $value; + } +}