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; } }