diff --git a/app/Jobs/SendStatementEmailJob.php b/app/Jobs/SendStatementEmailJob.php index 6984a11..1c2c83f 100644 --- a/app/Jobs/SendStatementEmailJob.php +++ b/app/Jobs/SendStatementEmailJob.php @@ -1,418 +1,425 @@ period = $period; - $this->requestType = $requestType; - $this->targetValue = $targetValue; - $this->batchId = $batchId ?? uniqid('batch_'); - $this->logId = $logId; + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - Log::info('SendStatementEmailJob created', [ - 'period' => $this->period, - 'request_type' => $this->requestType, - 'target_value' => $this->targetValue, - 'batch_id' => $this->batchId, - 'log_id' => $this->logId - ]); - } + protected $period; + protected $requestType; + protected $targetValue; // account_number, branch_code, atau null untuk all + protected $batchId; + protected $logId; - /** - * Menjalankan job pengiriman email statement - */ - public function handle(): void - { - Log::info('Starting SendStatementEmailJob execution', [ - 'batch_id' => $this->batchId, - 'period' => $this->period, - 'request_type' => $this->requestType, - 'target_value' => $this->targetValue - ]); + /** + * Membuat instance job baru + * + * @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, $requestType = 'single_account', $targetValue = null, $batchId = null, $logId = null) + { + $this->period = $period; + $this->requestType = $requestType; + $this->targetValue = $targetValue; + $this->batchId = $batchId ?? uniqid('batch_'); + $this->logId = $logId; - DB::beginTransaction(); + Log::info('SendStatementEmailJob created', [ + 'period' => $this->period, + 'request_type' => $this->requestType, + 'target_value' => $this->targetValue, + 'batch_id' => $this->batchId, + 'log_id' => $this->logId + ]); + } - try { - // Update log status menjadi processing - $this->updateLogStatus('processing', ['started_at' => now()]); + /** + * Menjalankan job pengiriman email statement + */ + public function handle() + : void + { + Log::info('Starting SendStatementEmailJob execution', [ + 'batch_id' => $this->batchId, + 'period' => $this->period, + 'request_type' => $this->requestType, + 'target_value' => $this->targetValue + ]); - // Ambil accounts berdasarkan request type - $accounts = $this->getAccountsByRequestType(); + DB::beginTransaction(); - if ($accounts->isEmpty()) { - Log::warning('No accounts with email found', [ - 'period' => $this->period, - 'request_type' => $this->requestType, - 'target_value' => $this->targetValue, - 'batch_id' => $this->batchId + try { + // 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, + '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() ]); - $this->updateLogStatus('completed', [ - 'completed_at' => now(), - 'total_accounts' => 0, - 'processed_accounts' => 0, - 'success_count' => 0, - 'failed_count' => 0 + $successCount = 0; + $failedCount = 0; + $processedCount = 0; + + foreach ($accounts as $account) { + try { + $this->sendStatementEmail($account); + $successCount++; + + Log::info('Statement email sent successfully', [ + 'account_number' => $account->account_number, + 'branch_code' => $account->branch_code, + 'email' => $this->getEmailForAccount($account), + 'batch_id' => $this->batchId + ]); + } catch (Exception $e) { + $failedCount++; + + Log::error('Failed to send statement email', [ + 'account_number' => $account->account_number, + 'branch_code' => $account->branch_code, + '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, + '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(), + 'trace' => $e->getTraceAsString() + ]); + + throw $e; + } + } + + /** + * Update status log + */ + private function updateLogStatus($status, $additionalData = []) + { + if (!$this->logId) { return; } - // Update total accounts - $this->updateLogStatus('processing', [ - 'total_accounts' => $accounts->count(), - 'target_accounts' => $accounts->pluck('account_number')->toArray() + 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 + */ + private function getAccountsByRequestType() + { + Log::info('Fetching accounts by request type', [ + 'period' => $this->period, + 'request_type' => $this->requestType, + 'target_value' => $this->targetValue ]); - $successCount = 0; - $failedCount = 0; - $processedCount = 0; + $query = Account::with('customer') + ->where('stmt_sent_type', 'BY.EMAIL'); - foreach ($accounts as $account) { - try { - $this->sendStatementEmail($account); - $successCount++; + switch ($this->requestType) { + case 'single_account': + if ($this->targetValue) { + $query->where('account_number', $this->targetValue); + } + break; - Log::info('Statement email sent successfully', [ - 'account_number' => $account->account_number, - 'branch_code' => $account->branch_code, - 'email' => $this->getEmailForAccount($account), - 'batch_id' => $this->batchId - ]); - } catch (\Exception $e) { - $failedCount++; + case 'branch': + if ($this->targetValue) { + $query->where('branch_code', $this->targetValue); + } + break; - Log::error('Failed to send statement email', [ - 'account_number' => $account->account_number, - 'branch_code' => $account->branch_code, - 'email' => $this->getEmailForAccount($account), - 'error' => $e->getMessage(), - 'batch_id' => $this->batchId - ]); - } + case 'all_branches': + // Tidak ada filter tambahan, ambil semua + break; - $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 - ]); - } + default: + throw new InvalidArgumentException("Invalid request type: {$this->requestType}"); } - // 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 + $accounts = $query->get(); + + // Filter accounts yang memiliki email + $accountsWithEmail = $accounts->filter(function ($account) { + 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 ]); - DB::commit(); + return $accountsWithEmail; + } - Log::info('SendStatementEmailJob completed', [ - 'batch_id' => $this->batchId, - 'total_accounts' => $accounts->count(), - 'success_count' => $successCount, - 'failed_count' => $failedCount, - 'final_status' => $finalStatus + /** + * 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 ]); - } catch (\Exception $e) { - DB::rollBack(); + 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 + * + * @param Account $account + * + * @return string|null + */ + private function getEmailForAccount(Account $account) + { + // Prioritas pertama: stmt_email dari account + if (!empty($account->stmt_email)) { + Log::info('Using stmt_email from account', [ + 'account_number' => $account->account_number, + 'email' => $account->stmt_email, + 'batch_id' => $this->batchId + ]); + return $account->stmt_email; + } + + // Prioritas kedua: email dari customer + if ($account->customer && !empty($account->customer->email)) { + Log::info('Using email from customer', [ + 'account_number' => $account->account_number, + 'customer_code' => $account->customer_code, + 'email' => $account->customer->email, + 'batch_id' => $this->batchId + ]); + return $account->customer->email; + } + + Log::warning('No email found for account', [ + 'account_number' => $account->account_number, + 'customer_code' => $account->customer_code, + 'batch_id' => $this->batchId + ]); + + return null; + } + + /** + * Mendapatkan path file PDF statement + * + * @param string $accountNumber + * @param string $branchCode + * + * @return string + */ + private function getPdfPath($accountNumber, $branchCode) + { + return "combine/{$this->period}/{$branchCode}/{$accountNumber}_{$this->period}.pdf"; + } + + /** + * Membuat atau update log statement + * + * @param Account $account + * + * @return PrintStatementLog + */ + private function createOrUpdateStatementLog(Account $account) + { + $emailAddress = $this->getEmailForAccount($account); + + $logData = [ + 'account_number' => $account->account_number, + 'customer_code' => $account->customer_code, + 'branch_code' => $account->branch_code, + 'period' => $this->period, + 'print_date' => now(), + 'batch_id' => $this->batchId, + 'email_address' => $emailAddress, + 'email_source' => !empty($account->stmt_email) ? 'account' : 'customer' + ]; + + $statementLog = PrintStatementLog::updateOrCreate( + [ + 'account_number' => $account->account_number, + 'period_from' => $this->period, + 'period_to' => $this->period + ], + $logData + ); + + Log::info('Statement log created/updated', [ + 'log_id' => $statementLog->id, + 'account_number' => $account->account_number, + 'email_address' => $emailAddress, + 'batch_id' => $this->batchId + ]); + + return $statementLog; + } + + /** + * Handle job failure + */ + public function failed(Throwable $exception) + { $this->updateLogStatus('failed', [ - 'completed_at' => now(), - 'error_message' => $e->getMessage() + 'completed_at' => now(), + 'error_message' => $exception->getMessage() ]); - Log::error('SendStatementEmailJob failed', [ - 'batch_id' => $this->batchId, - 'error' => $e->getMessage(), - 'trace' => $e->getTraceAsString() - ]); - - throw $e; - } - } - - /** - * Mengambil accounts berdasarkan request type - */ - private function getAccountsByRequestType() - { - Log::info('Fetching accounts by request type', [ - 'period' => $this->period, - 'request_type' => $this->requestType, - 'target_value' => $this->targetValue - ]); - - $query = Account::with('customer') - ->where('stmt_sent_type', 'BY.EMAIL'); - - 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}"); - } - - $accounts = $query->get(); - - // Filter accounts yang memiliki email - $accountsWithEmail = $accounts->filter(function ($account) { - 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() + Log::error('SendStatementEmailJob failed permanently', [ + 'batch_id' => $this->batchId, + 'period' => $this->period, + 'request_type' => $this->requestType, + 'target_value' => $this->targetValue, + 'error' => $exception->getMessage(), + 'trace' => $exception->getTraceAsString() ]); } } - - /** - * Mendapatkan email untuk pengiriman statement - * - * @param Account $account - * @return string|null - */ - private function getEmailForAccount(Account $account) - { - // Prioritas pertama: stmt_email dari account - if (!empty($account->stmt_email)) { - Log::info('Using stmt_email from account', [ - 'account_number' => $account->account_number, - 'email' => $account->stmt_email, - 'batch_id' => $this->batchId - ]); - return $account->stmt_email; - } - - // Prioritas kedua: email dari customer - if ($account->customer && !empty($account->customer->email)) { - Log::info('Using email from customer', [ - 'account_number' => $account->account_number, - 'customer_code' => $account->customer_code, - 'email' => $account->customer->email, - 'batch_id' => $this->batchId - ]); - return $account->customer->email; - } - - Log::warning('No email found for account', [ - 'account_number' => $account->account_number, - 'customer_code' => $account->customer_code, - 'batch_id' => $this->batchId - ]); - - 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 - * - * @param string $accountNumber - * @param string $branchCode - * @return string - */ - private function getPdfPath($accountNumber, $branchCode) - { - return "combine/{$this->period}/{$branchCode}/{$accountNumber}_{$this->period}.pdf"; - } - - /** - * Membuat atau update log statement - * - * @param Account $account - * @return PrintStatementLog - */ - private function createOrUpdateStatementLog(Account $account) - { - $emailAddress = $this->getEmailForAccount($account); - - $logData = [ - 'account_number' => $account->account_number, - 'customer_code' => $account->customer_code, - 'branch_code' => $account->branch_code, - 'period' => $this->period, - 'print_date' => now(), - 'batch_id' => $this->batchId, - 'email_address' => $emailAddress, - 'email_source' => !empty($account->stmt_email) ? 'account' : 'customer' - ]; - - $statementLog = PrintStatementLog::updateOrCreate( - [ - 'account_number' => $account->account_number, - 'period_from' => $this->period, - 'period_to' => $this->period - ], - $logData - ); - - Log::info('Statement log created/updated', [ - 'log_id' => $statementLog->id, - 'account_number' => $account->account_number, - 'email_address' => $emailAddress, - 'batch_id' => $this->batchId - ]); - - return $statementLog; - } - - /** - * Handle job failure - */ - 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, - 'request_type' => $this->requestType, - 'target_value' => $this->targetValue, - 'error' => $exception->getMessage(), - 'trace' => $exception->getTraceAsString() - ]); - } -}