feat(webstatement): tambahkan fitur monitoring dan peningkatan pengiriman email statement
- **Perbaikan dan Penambahan Komando:**
- Memberikan komando baru `webstatement:check-progress` untuk memantau progres pengiriman email statement.
- Menampilkan informasi seperti `Log ID`, `Batch ID`, `Request Type`, status, hingga persentase progress.
- Menangani secara detail jumlah akun yang diproses, sukses, gagal, dan kalkulasi tingkat keberhasilan.
- Menyediakan penanganan error jika log tidak ditemukan atau terjadi kegagalan lainnya.
- Memperluas komando `webstatement:send-email`:
- Mendukung pengiriman berdasarkan `single account`, `branch`, atau `all branches`.
- Menambahkan validasi parameter `type` (`single`, `branch`, `all`) dan input spesifik seperti `--account` atau `--branch` untuk mode tertentu.
- Melakukan pencatatan log awal dengan metadata lengkap seperti `request_type`, `batch_id`, dan status.
- **Peningkatan Logika Proses Backend:**
- Menambahkan fungsi `createLogEntry` untuk mencatat log pengiriman email statement secara dinamis berdasarkan tipe request.
- Menyediakan reusable method seperti `validateParameters` dan `determineRequestTypeAndTarget` untuk mempermudah pengelolaan parameter pengiriman.
- Memberikan feedback dan panduan kepada pengguna mengenai ID log dan komando monitoring (`webstatement:check-progress`).
- **Penambahan Controller dan Fitur UI:**
- Menambahkan controller baru `EmailStatementLogController`:
- Mendukung pengelolaan log seperti list, detail, dan retry untuk pengiriman ulang email statement.
- Menyediakan fitur pencarian, filter, dan halaman data log yang responsif menggunakan datatable.
- Menambahkan kemampuan resend email untuk log dengan status `completed` atau `failed`.
- Mengimplementasikan UI untuk log pengiriman:
- Halaman daftar monitoring dengan filter berdasarkan branch, account number, request type, status, dan tanggal.
- Menampilkan kemajuan, tingkat keberhasilan, serta tombol aksi seperti detail dan pengiriman ulang.
- **Peningkatan Model dan Validasi:**
- Menyesuaikan model `PrintStatementLog` untuk mendukung lebih banyak atribut seperti `processed_accounts`, `success_count`, `failed_count`, `request_type`, serta metode utilitas seperti `getProgressPercentage()` dan `getSuccessRate()`.
- Memvalidasi parameter input lebih mendalam agar kesalahan dapat diminimalisasi di awal proses.
- **Peningkatan pada View dan Feedback Pengguna:**
- Menambah daftar command berguna untuk user di interface log:
- Status antrian dengan `php artisan queue:work`.
- Monitoring menggunakan komando custom yang baru ditambahkan.
- **Perbaikan Logging dan Error Handling:**
- Menambahkan logging komprehensif pada semua proses, termasuk batch pengiriman ulang.
- Memastikan rollback pada database jika terjadi error melalui transaksi pada critical path.
Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
This commit is contained in:
@@ -14,75 +14,98 @@ use Illuminate\Support\Facades\Storage;
|
||||
use Modules\Webstatement\Models\Account;
|
||||
use Modules\Webstatement\Models\PrintStatementLog;
|
||||
use Modules\Webstatement\Mail\StatementEmail;
|
||||
use Modules\Basicdata\Models\Branch;
|
||||
|
||||
/**
|
||||
* Job untuk mengirim email PDF statement ke nasabah
|
||||
*
|
||||
* Job ini akan:
|
||||
* 1. Mengambil data account yang memiliki email
|
||||
* 2. Mencari file PDF statement di storage
|
||||
* 3. Mengirim email dengan attachment PDF
|
||||
* 4. Mencatat log pengiriman
|
||||
* Mendukung pengiriman per rekening, per cabang, atau seluruh cabang
|
||||
*/
|
||||
class SendStatementEmailJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $period;
|
||||
protected $accountNumber;
|
||||
protected $requestType;
|
||||
protected $targetValue; // account_number, branch_code, atau null untuk all
|
||||
protected $batchId;
|
||||
protected $logId;
|
||||
|
||||
/**
|
||||
* Membuat instance job baru
|
||||
*
|
||||
* @param string $period Format: YYYY-MM
|
||||
* @param string|null $accountNumber Nomor rekening spesifik (opsional)
|
||||
* @param string $period Format: YYYYMM
|
||||
* @param string $requestType 'single_account', 'branch', 'all_branches'
|
||||
* @param string|null $targetValue account_number untuk single, branch_code untuk branch, null untuk all
|
||||
* @param string|null $batchId ID batch untuk tracking
|
||||
* @param int|null $logId ID log untuk update progress
|
||||
*/
|
||||
public function __construct($period, $accountNumber = null, $batchId = null)
|
||||
public function __construct($period, $requestType = 'single_account', $targetValue = null, $batchId = null, $logId = null)
|
||||
{
|
||||
$this->period = $period;
|
||||
$this->accountNumber = $accountNumber;
|
||||
$this->requestType = $requestType;
|
||||
$this->targetValue = $targetValue;
|
||||
$this->batchId = $batchId ?? uniqid('batch_');
|
||||
$this->logId = $logId;
|
||||
|
||||
Log::info('SendStatementEmailJob created', [
|
||||
'period' => $this->period,
|
||||
'account_number' => $this->accountNumber,
|
||||
'batch_id' => $this->batchId
|
||||
'request_type' => $this->requestType,
|
||||
'target_value' => $this->targetValue,
|
||||
'batch_id' => $this->batchId,
|
||||
'log_id' => $this->logId
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Menjalankan job pengiriman email statement
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
Log::info('Starting SendStatementEmailJob execution', [
|
||||
'batch_id' => $this->batchId,
|
||||
'period' => $this->period,
|
||||
'account_number' => $this->accountNumber
|
||||
'request_type' => $this->requestType,
|
||||
'target_value' => $this->targetValue
|
||||
]);
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
// Ambil accounts yang memiliki email
|
||||
$accounts = $this->getAccountsWithEmail();
|
||||
// Update log status menjadi processing
|
||||
$this->updateLogStatus('processing', ['started_at' => now()]);
|
||||
|
||||
// Ambil accounts berdasarkan request type
|
||||
$accounts = $this->getAccountsByRequestType();
|
||||
|
||||
if ($accounts->isEmpty()) {
|
||||
Log::warning('No accounts with email found', [
|
||||
'period' => $this->period,
|
||||
'account_number' => $this->accountNumber,
|
||||
'request_type' => $this->requestType,
|
||||
'target_value' => $this->targetValue,
|
||||
'batch_id' => $this->batchId
|
||||
]);
|
||||
|
||||
$this->updateLogStatus('completed', [
|
||||
'completed_at' => now(),
|
||||
'total_accounts' => 0,
|
||||
'processed_accounts' => 0,
|
||||
'success_count' => 0,
|
||||
'failed_count' => 0
|
||||
]);
|
||||
|
||||
DB::commit();
|
||||
return;
|
||||
}
|
||||
|
||||
// Update total accounts
|
||||
$this->updateLogStatus('processing', [
|
||||
'total_accounts' => $accounts->count(),
|
||||
'target_accounts' => $accounts->pluck('account_number')->toArray()
|
||||
]);
|
||||
|
||||
$successCount = 0;
|
||||
$failedCount = 0;
|
||||
$processedCount = 0;
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
try {
|
||||
@@ -92,7 +115,7 @@ class SendStatementEmailJob implements ShouldQueue
|
||||
Log::info('Statement email sent successfully', [
|
||||
'account_number' => $account->account_number,
|
||||
'branch_code' => $account->branch_code,
|
||||
'email' => $account->stmt_email,
|
||||
'email' => $this->getEmailForAccount($account),
|
||||
'batch_id' => $this->batchId
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
@@ -101,25 +124,51 @@ class SendStatementEmailJob implements ShouldQueue
|
||||
Log::error('Failed to send statement email', [
|
||||
'account_number' => $account->account_number,
|
||||
'branch_code' => $account->branch_code,
|
||||
'email' => $account->stmt_email,
|
||||
'email' => $this->getEmailForAccount($account),
|
||||
'error' => $e->getMessage(),
|
||||
'batch_id' => $this->batchId
|
||||
]);
|
||||
}
|
||||
|
||||
$processedCount++;
|
||||
|
||||
// Update progress setiap 10 account atau di akhir
|
||||
if ($processedCount % 10 === 0 || $processedCount === $accounts->count()) {
|
||||
$this->updateLogStatus('processing', [
|
||||
'processed_accounts' => $processedCount,
|
||||
'success_count' => $successCount,
|
||||
'failed_count' => $failedCount
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Update status final
|
||||
$finalStatus = $failedCount === 0 ? 'completed' : ($successCount === 0 ? 'failed' : 'completed');
|
||||
$this->updateLogStatus($finalStatus, [
|
||||
'completed_at' => now(),
|
||||
'processed_accounts' => $processedCount,
|
||||
'success_count' => $successCount,
|
||||
'failed_count' => $failedCount
|
||||
]);
|
||||
|
||||
DB::commit();
|
||||
|
||||
Log::info('SendStatementEmailJob completed', [
|
||||
'batch_id' => $this->batchId,
|
||||
'total_accounts' => $accounts->count(),
|
||||
'success_count' => $successCount,
|
||||
'failed_count' => $failedCount
|
||||
'failed_count' => $failedCount,
|
||||
'final_status' => $finalStatus
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
|
||||
$this->updateLogStatus('failed', [
|
||||
'completed_at' => now(),
|
||||
'error_message' => $e->getMessage()
|
||||
]);
|
||||
|
||||
Log::error('SendStatementEmailJob failed', [
|
||||
'batch_id' => $this->batchId,
|
||||
'error' => $e->getMessage(),
|
||||
@@ -131,52 +180,79 @@ class SendStatementEmailJob implements ShouldQueue
|
||||
}
|
||||
|
||||
/**
|
||||
* Mengambil accounts yang memiliki email dan sesuai kriteria
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
* Mengambil accounts berdasarkan request type
|
||||
*/
|
||||
private function getAccountsWithEmail()
|
||||
private function getAccountsByRequestType()
|
||||
{
|
||||
Log::info('Fetching accounts with email', [
|
||||
Log::info('Fetching accounts by request type', [
|
||||
'period' => $this->period,
|
||||
'account_number' => $this->accountNumber
|
||||
'request_type' => $this->requestType,
|
||||
'target_value' => $this->targetValue
|
||||
]);
|
||||
|
||||
$query = Account::with('customer')
|
||||
->where('stmt_sent_type', 'BY.EMAIL');
|
||||
|
||||
// Jika account number spesifik diberikan
|
||||
if ($this->accountNumber) {
|
||||
$query->where('account_number', $this->accountNumber);
|
||||
switch ($this->requestType) {
|
||||
case 'single_account':
|
||||
if ($this->targetValue) {
|
||||
$query->where('account_number', $this->targetValue);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'branch':
|
||||
if ($this->targetValue) {
|
||||
$query->where('branch_code', $this->targetValue);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'all_branches':
|
||||
// Tidak ada filter tambahan, ambil semua
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException("Invalid request type: {$this->requestType}");
|
||||
}
|
||||
|
||||
// Ambil semua accounts yang memenuhi kriteria
|
||||
$accounts = $query->get();
|
||||
|
||||
// Filter accounts yang memiliki email (dari stmt_email atau customer email)
|
||||
// Filter accounts yang memiliki email
|
||||
$accountsWithEmail = $accounts->filter(function ($account) {
|
||||
// Cek apakah stmt_email ada dan tidak kosong
|
||||
if (!empty($account->stmt_email)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Jika stmt_email kosong, cek email di customer
|
||||
if ($account->customer && !empty($account->customer->email)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return !empty($account->stmt_email) ||
|
||||
($account->customer && !empty($account->customer->email));
|
||||
});
|
||||
|
||||
Log::info('Accounts with email retrieved', [
|
||||
'total_accounts' => $accounts->count(),
|
||||
'accounts_with_email' => $accountsWithEmail->count(),
|
||||
'request_type' => $this->requestType,
|
||||
'batch_id' => $this->batchId
|
||||
]);
|
||||
|
||||
return $accountsWithEmail;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mendapatkan email untuk pengiriman statement
|
||||
*
|
||||
@@ -245,6 +321,8 @@ class SendStatementEmailJob implements ShouldQueue
|
||||
$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)
|
||||
);
|
||||
@@ -320,16 +398,19 @@ class SendStatementEmailJob implements ShouldQueue
|
||||
|
||||
/**
|
||||
* Handle job failure
|
||||
*
|
||||
* @param \Throwable $exception
|
||||
* @return void
|
||||
*/
|
||||
public function failed(\Throwable $exception)
|
||||
{
|
||||
$this->updateLogStatus('failed', [
|
||||
'completed_at' => now(),
|
||||
'error_message' => $exception->getMessage()
|
||||
]);
|
||||
|
||||
Log::error('SendStatementEmailJob failed permanently', [
|
||||
'batch_id' => $this->batchId,
|
||||
'period' => $this->period,
|
||||
'account_number' => $this->accountNumber,
|
||||
'request_type' => $this->requestType,
|
||||
'target_value' => $this->targetValue,
|
||||
'error' => $exception->getMessage(),
|
||||
'trace' => $exception->getTraceAsString()
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user