period = $period; $this->requestType = $requestType; $this->targetValue = $targetValue; $this->batchId = $batchId ?? uniqid('batch_'); $this->logId = $logId; Log::info('SendStatementEmailJob created', [ 'period' => $this->period, 'request_type' => $this->requestType, 'target_value' => $this->targetValue, 'batch_id' => $this->batchId, 'log_id' => $this->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 ]); DB::beginTransaction(); 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() ]); $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; } 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 ]); $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': $query->orderBy('branch_code', 'asc'); // Tidak ada filter tambahan, ambil semua break; default: throw new InvalidArgumentException("Invalid request type: {$this->requestType}"); } $query->orderBy('account_number'); $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; } /** * 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 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' => $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() ]); } }