refactor(webstatement): restrukturisasi kode pada SendStatementEmailJob

- **Perbaikan Struktural:**
  - Melakukan perapihan kode dengan konsistensi indentasi dan penyusunan namespace seluruh file.
  - Menambahkan dan mengimpor namespace baru seperti `Throwable`, `InvalidArgumentException`, dan `Exception`.

- **Peningkatan Readability:**
  - Menambahkan format dan penyesuaian pada komentar, khususnya penjelasan method dan atribut.
  - Menggunakan alignment untuk parameter pada log dan constructor untuk meningkatkan keterbacaan.

- **Pengelolaan Job:**
  - Menambahkan logging detail saat memulai, menjalankan, hingga menyelesaikan job.
  - Menambahkan penanganan proses tiap akun dalam batch, termasuk logging sukses/gagal dan pembaruan status log.

- **Penanganan Error:**
  - Menambahkan rollback database jika terjadi exception pada saat proses pengiriman email.
  - Melakukan logging error dengan detail tambahan termasuk pesan dan trace.

- **Penambahan Utility:**
  - Menambahkan metode reusable seperti `updateLogStatus` untuk update status log dengan parameter dinamis.
  - Menambahkan validasi seperti pengecekan eksistensi file PDF dan email terkait sebelum pengiriman.

- **Peningkatan Proses Batch:**
  - Menambahkan pengelolaan batch berbasis properti `batchId` untuk tracking.
  - Memperbaiki handle retries dan status akhir batch secara komprehensif (completed, failed).
  - Menambahkan logging agregat untuk jumlah akun yang diproses dan tingkat keberhasilan.

- **Peningkatan Validasi Email:**
  - Menambahkan skenario untuk pengambilan email dari `stmt_email` atau fallback ke data customer jika tersedia.
  - Menambahkan peringatan saat akun tertentu tidak memiliki email yang valid.

- **Pemeliharaan File PDF:**
  - Mengecek keberadaan file PDF terkait sebelum proses pengiriman.
  - Menampilkan log error jika file tidak ditemukan.

- **Peningkatan Retry dan Logging Gagal:**
  - Implementasi metode `failed()` untuk logging pada job yang gagal permanen.
  - Menangani detail error dan rollback pada level tiap akun dan batch.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
This commit is contained in:
Daeng Deni Mardaeni
2025-06-12 09:18:16 +07:00
parent f6df453ddc
commit d5482fb824

View File

@@ -2,6 +2,7 @@
namespace Modules\Webstatement\Jobs; namespace Modules\Webstatement\Jobs;
use Exception;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
@@ -11,10 +12,11 @@ use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use InvalidArgumentException;
use Modules\Webstatement\Mail\StatementEmail;
use Modules\Webstatement\Models\Account; use Modules\Webstatement\Models\Account;
use Modules\Webstatement\Models\PrintStatementLog; use Modules\Webstatement\Models\PrintStatementLog;
use Modules\Webstatement\Mail\StatementEmail; use Throwable;
use Modules\Basicdata\Models\Branch;
/** /**
* Job untuk mengirim email PDF statement ke nasabah * Job untuk mengirim email PDF statement ke nasabah
@@ -59,7 +61,8 @@ class SendStatementEmailJob implements ShouldQueue
/** /**
* Menjalankan job pengiriman email statement * Menjalankan job pengiriman email statement
*/ */
public function handle(): void public function handle()
: void
{ {
Log::info('Starting SendStatementEmailJob execution', [ Log::info('Starting SendStatementEmailJob execution', [
'batch_id' => $this->batchId, 'batch_id' => $this->batchId,
@@ -118,7 +121,7 @@ class SendStatementEmailJob implements ShouldQueue
'email' => $this->getEmailForAccount($account), 'email' => $this->getEmailForAccount($account),
'batch_id' => $this->batchId 'batch_id' => $this->batchId
]); ]);
} catch (\Exception $e) { } catch (Exception $e) {
$failedCount++; $failedCount++;
Log::error('Failed to send statement email', [ Log::error('Failed to send statement email', [
@@ -161,7 +164,7 @@ class SendStatementEmailJob implements ShouldQueue
'final_status' => $finalStatus 'final_status' => $finalStatus
]); ]);
} catch (\Exception $e) { } catch (Exception $e) {
DB::rollBack(); DB::rollBack();
$this->updateLogStatus('failed', [ $this->updateLogStatus('failed', [
@@ -179,6 +182,27 @@ class SendStatementEmailJob implements ShouldQueue
} }
} }
/**
* Update status log
*/
private function updateLogStatus($status, $additionalData = [])
{
if (!$this->logId) {
return;
}
try {
$updateData = array_merge(['status' => $status], $additionalData);
PrintStatementLog::where('id', $this->logId)->update($updateData);
} catch (Exception $e) {
Log::error('Failed to update log status', [
'log_id' => $this->logId,
'status' => $status,
'error' => $e->getMessage()
]);
}
}
/** /**
* Mengambil accounts berdasarkan request type * Mengambil accounts berdasarkan request type
*/ */
@@ -211,7 +235,7 @@ class SendStatementEmailJob implements ShouldQueue
break; break;
default: default:
throw new \InvalidArgumentException("Invalid request type: {$this->requestType}"); throw new InvalidArgumentException("Invalid request type: {$this->requestType}");
} }
$accounts = $query->get(); $accounts = $query->get();
@@ -233,30 +257,64 @@ class SendStatementEmailJob implements ShouldQueue
} }
/** /**
* Update status log * Mengirim email statement untuk account tertentu
*
* @param Account $account
*
* @return void
* @throws \Exception
*/ */
private function updateLogStatus($status, $additionalData = []) private function sendStatementEmail(Account $account)
{ {
if (!$this->logId) { // Dapatkan email untuk pengiriman
return; $emailAddress = $this->getEmailForAccount($account);
if (!$emailAddress) {
throw new Exception("No email address found for account {$account->account_number}");
} }
try { // Cek apakah file PDF ada
$updateData = array_merge(['status' => $status], $additionalData); $pdfPath = $this->getPdfPath($account->account_number, $account->branch_code);
PrintStatementLog::where('id', $this->logId)->update($updateData);
} catch (\Exception $e) { if (!Storage::exists($pdfPath)) {
Log::error('Failed to update log status', [ throw new Exception("PDF file not found: {$pdfPath}");
'log_id' => $this->logId,
'status' => $status,
'error' => $e->getMessage()
]);
} }
// Buat atau update log statement
$statementLog = $this->createOrUpdateStatementLog($account);
// Dapatkan path absolut file
$absolutePdfPath = Storage::path($pdfPath);
// Kirim email
// Add delay between email sends to prevent rate limiting
sleep(1); // 2 second delay
Mail::to($emailAddress)->send(
new StatementEmail($statementLog, $absolutePdfPath, false)
);
// Update status log dengan email yang digunakan
$statementLog->update([
'email_sent_at' => now(),
'email_status' => 'sent',
'email_address' => $emailAddress // Simpan email yang digunakan untuk tracking
]);
Log::info('Email sent for account', [
'account_number' => $account->account_number,
'branch_code' => $account->branch_code,
'email' => $emailAddress,
'email_source' => !empty($account->stmt_email) ? 'account.stmt_email' : 'customer.email',
'pdf_path' => $pdfPath,
'batch_id' => $this->batchId
]);
} }
/** /**
* Mendapatkan email untuk pengiriman statement * Mendapatkan email untuk pengiriman statement
* *
* @param Account $account * @param Account $account
*
* @return string|null * @return string|null
*/ */
private function getEmailForAccount(Account $account) private function getEmailForAccount(Account $account)
@@ -291,64 +349,12 @@ class SendStatementEmailJob implements ShouldQueue
return null; return null;
} }
/**
* Mengirim email statement untuk account tertentu
*
* @param Account $account
* @return void
* @throws \Exception
*/
private function sendStatementEmail(Account $account)
{
// Dapatkan email untuk pengiriman
$emailAddress = $this->getEmailForAccount($account);
if (!$emailAddress) {
throw new \Exception("No email address found for account {$account->account_number}");
}
// Cek apakah file PDF ada
$pdfPath = $this->getPdfPath($account->account_number, $account->branch_code);
if (!Storage::exists($pdfPath)) {
throw new \Exception("PDF file not found: {$pdfPath}");
}
// Buat atau update log statement
$statementLog = $this->createOrUpdateStatementLog($account);
// Dapatkan path absolut file
$absolutePdfPath = Storage::path($pdfPath);
// Kirim email
// Add delay between email sends to prevent rate limiting
sleep(1); // 2 second delay
Mail::to($emailAddress)->send(
new StatementEmail($statementLog, $absolutePdfPath, false)
);
// Update status log dengan email yang digunakan
$statementLog->update([
'email_sent_at' => now(),
'email_status' => 'sent',
'email_address' => $emailAddress // Simpan email yang digunakan untuk tracking
]);
Log::info('Email sent for account', [
'account_number' => $account->account_number,
'branch_code' => $account->branch_code,
'email' => $emailAddress,
'email_source' => !empty($account->stmt_email) ? 'account.stmt_email' : 'customer.email',
'pdf_path' => $pdfPath,
'batch_id' => $this->batchId
]);
}
/** /**
* Mendapatkan path file PDF statement * Mendapatkan path file PDF statement
* *
* @param string $accountNumber * @param string $accountNumber
* @param string $branchCode * @param string $branchCode
*
* @return string * @return string
*/ */
private function getPdfPath($accountNumber, $branchCode) private function getPdfPath($accountNumber, $branchCode)
@@ -360,6 +366,7 @@ class SendStatementEmailJob implements ShouldQueue
* Membuat atau update log statement * Membuat atau update log statement
* *
* @param Account $account * @param Account $account
*
* @return PrintStatementLog * @return PrintStatementLog
*/ */
private function createOrUpdateStatementLog(Account $account) private function createOrUpdateStatementLog(Account $account)
@@ -399,7 +406,7 @@ class SendStatementEmailJob implements ShouldQueue
/** /**
* Handle job failure * Handle job failure
*/ */
public function failed(\Throwable $exception) public function failed(Throwable $exception)
{ {
$this->updateLogStatus('failed', [ $this->updateLogStatus('failed', [
'completed_at' => now(), 'completed_at' => now(),