Files
webstatement/app/Jobs/GenerateAtmTransactionReportJob.php
Daeng Deni Mardaeni 903cbd1725 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>
2025-06-08 23:40:52 +07:00

157 lines
5.4 KiB
PHP

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