- Menambahkan konstanta baru pada setiap job untuk meningkatkan keterbacaan: - `CSV_DELIMITER` untuk delimiter CSV. - `MAX_EXECUTION_TIME` untuk batas waktu eksekusi (86400 detik). - `FILENAME` untuk nama file masing-masing job. - `DISK_NAME` untuk disk yang digunakan. - Mengubah `protected` menjadi `private` untuk properti seperti: - `$period`, `$processedCount`, dan `$errorCount` di semua job. - Memindahkan logika proses menjadi metode modular untuk meningkatkan modularitas: - Metode `initializeJob` untuk inisialisasi counter. - Metode `processPeriod` untuk menangani file dan memulai proses. - Metode `validateFile` untuk validasi keberadaan file. - Metode `createTemporaryFile` untuk menyalin file sementara. - Metode `processFile` untuk membaca isi file CSV dan memproses. - Metode `processRow`/`mapAndSaveRecord`/`saveRecord` untuk pemrosesan dan penyimpanan data. - Metode `cleanup` untuk menghapus file sementara. - Metode `logJobCompletion` untuk logging hasil akhir. - Menerapkan pengolahan file dengan sistem logging terperinci: - Logging row dengan kolom tidak sesuai akan menghasilkan peringatan. - Mencatat jumlah record berhasil diproses serta error. Refaktor ini bertujuan untuk meningkatkan kualitas kode melalui modularisasi, keterbacaan, dan kemudahan pengujian ulang di seluruh kelas job.
169 lines
5.4 KiB
PHP
169 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\Log;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Modules\Webstatement\Models\TempArrangement;
|
|
|
|
class ProcessArrangementDataJob implements ShouldQueue
|
|
{
|
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
|
|
private const CSV_DELIMITER = '~';
|
|
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
|
private const FILENAME = 'ST.AA.ARRANGEMENT.csv';
|
|
private const DISK_NAME = 'sftpStatement';
|
|
|
|
private string $period;
|
|
private int $processedCount = 0;
|
|
private int $errorCount = 0;
|
|
|
|
/**
|
|
* Create a new job instance.
|
|
*/
|
|
public function __construct(string $period = '')
|
|
{
|
|
$this->period = $period;
|
|
}
|
|
|
|
/**
|
|
* Execute the job.
|
|
*/
|
|
public function handle()
|
|
: void
|
|
{
|
|
try {
|
|
$this->initializeJob();
|
|
|
|
if ($this->period === '') {
|
|
Log::warning('No period provided for arrangement data processing');
|
|
return;
|
|
}
|
|
|
|
$this->processPeriod();
|
|
$this->logJobCompletion();
|
|
} catch (Exception $e) {
|
|
Log::error('Error in ProcessArrangementDataJob: ' . $e->getMessage());
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
private function initializeJob()
|
|
: void
|
|
{
|
|
set_time_limit(self::MAX_EXECUTION_TIME);
|
|
$this->processedCount = 0;
|
|
$this->errorCount = 0;
|
|
}
|
|
|
|
private function processPeriod()
|
|
: void
|
|
{
|
|
$disk = Storage::disk(self::DISK_NAME);
|
|
$filename = "{$this->period}." . self::FILENAME;
|
|
$filePath = "{$this->period}/$filename";
|
|
|
|
if (!$this->validateFile($disk, $filePath)) {
|
|
return;
|
|
}
|
|
|
|
$tempFilePath = $this->createTemporaryFile($disk, $filePath, $filename);
|
|
$this->processFile($tempFilePath, $filePath);
|
|
$this->cleanup($tempFilePath);
|
|
}
|
|
|
|
private function validateFile($disk, string $filePath)
|
|
: bool
|
|
{
|
|
Log::info("Processing arrangement file: $filePath");
|
|
|
|
if (!$disk->exists($filePath)) {
|
|
Log::warning("File not found: $filePath");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private function createTemporaryFile($disk, string $filePath, string $filename)
|
|
: string
|
|
{
|
|
$tempFilePath = storage_path("app/temp_$filename");
|
|
file_put_contents($tempFilePath, $disk->get($filePath));
|
|
return $tempFilePath;
|
|
}
|
|
|
|
private function processFile(string $tempFilePath, string $filePath)
|
|
: void
|
|
{
|
|
$handle = fopen($tempFilePath, "r");
|
|
if ($handle === false) {
|
|
Log::error("Unable to open file: $filePath");
|
|
return;
|
|
}
|
|
|
|
$headers = (new TempArrangement())->getFillable();
|
|
$rowCount = 0;
|
|
|
|
while (($row = fgetcsv($handle, 0, self::CSV_DELIMITER)) !== false) {
|
|
$rowCount++;
|
|
$this->processRow($row, $headers, $rowCount, $filePath);
|
|
}
|
|
|
|
fclose($handle);
|
|
Log::info("Completed processing $filePath. Processed {$this->processedCount} records with {$this->errorCount} errors.");
|
|
}
|
|
|
|
private function processRow(array $row, array $headers, int $rowCount, string $filePath)
|
|
: void
|
|
{
|
|
if (count($headers) !== count($row)) {
|
|
Log::warning("Row $rowCount in $filePath has incorrect column count. Expected: " .
|
|
count($headers) . ", Got: " . count($row));
|
|
return;
|
|
}
|
|
|
|
$data = array_combine($headers, $row);
|
|
$this->saveRecord($data, $rowCount, $filePath);
|
|
}
|
|
|
|
private function saveRecord(array $data, int $rowCount, string $filePath)
|
|
: void
|
|
{
|
|
try {
|
|
if ($data['arrangement_id'] !== 'arrangement_id') {
|
|
TempArrangement::updateOrCreate(
|
|
['arrangement_id' => $data['arrangement_id']], // key to find existing record
|
|
$data // data to update or create
|
|
);
|
|
$this->processedCount++;
|
|
}
|
|
} catch (Exception $e) {
|
|
$this->errorCount++;
|
|
Log::error("Error processing Arrangement at row $rowCount in $filePath: " . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
private function cleanup(string $tempFilePath)
|
|
: void
|
|
{
|
|
if (file_exists($tempFilePath)) {
|
|
unlink($tempFilePath);
|
|
}
|
|
}
|
|
|
|
private function logJobCompletion()
|
|
: void
|
|
{
|
|
Log::info("Arrangement data processing completed. " .
|
|
"Total processed: {$this->processedCount}, Total errors: {$this->errorCount}");
|
|
}
|
|
}
|