feat(webstatement): tambahkan job untuk menghasilkan laporan transaksi ATM
- Menambahkan class baru `GenerateAtmTransactionReportJob` untuk menghasilkan laporan transaksi ATM dalam format CSV. - Memproses transaksi ATM berdasarkan periode tertentu dengan pagination menggunakan `chunk`. - Mendukung inisialisasi log laporan menggunakan model `AtmTransactionReportLog`. - Menyertakan fitur untuk menulis header CSV dan data transaksi, termasuk handling escape karakter pada nilai CSV. - Memperkenalkan direktori penyimpanan baru: - Path: `reports/atm_transactions/<period>.csv`. - Membuat direktori jika belum ada sebelum menyimpan laporan. - Menambahkan logging: - Menyertakan informasi waktu mulai, lokasi file laporan CSV, dan jumlah transaksi yang diproses. - Menangani error dengan logging error message dan memperbarui status log laporan. - Menambahkan logika pembaruan log laporan: - Field `status`, `file_path`, `file_size`, dan `record_count` akan diperbarui setelah proses selesai. - Handling error pada log laporan jika proses gagal. - Fitur tambahan: - Handling escape untuk nilai CSV guna memastikan format tetap valid. - Menangani error dengan throwing exception jika terjadi masalah selama proses. Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
This commit is contained in:
156
app/Jobs/GenerateAtmTransactionReportJob.php
Normal file
156
app/Jobs/GenerateAtmTransactionReportJob.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Webstatement\Jobs;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Modules\Webstatement\Models\AtmTransaction;
|
||||
use Modules\Webstatement\Models\AtmTransactionReportLog;
|
||||
|
||||
class GenerateAtmTransactionReportJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
private string $period;
|
||||
private const CHUNK_SIZE = 1000;
|
||||
private const CSV_DELIMITER = ',';
|
||||
private ?int $reportLogId;
|
||||
|
||||
public function __construct(string $period, ?int $reportLogId = null)
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user