From f3c649572b2521f0089ad76917601481c2272d21 Mon Sep 17 00:00:00 2001 From: Daeng Deni Mardaeni Date: Tue, 10 Jun 2025 13:50:00 +0700 Subject: [PATCH] feat(webstatement): tambahkan fitur pengiriman email statement PDF - Menambahkan Command `SendStatementEmailCommand` untuk mengirim email statement PDF: - Mendukung parameter input seperti periode laporan (`YYYY-MM`), nomor rekening, ID batch, queue, dan delay waktu. - Menjalankan validasi parameter input, mencatat log eksekusi, dan mendispatch job pengiriman email. - Menyediakan feedback status eksekusi serta informasi job kepada user. - Menambahkan Job `SendStatementEmailJob` untuk pengiriman statement dalam latar belakang: - Memfilter account yang memiliki email terkait, baik dari `stmt_email` atau email dari data customer. - Melakukan pengiriman email dengan attachment file PDF statement. - Mencatat log sukses atau kegagalan pengiriman untuk setiap account. - Memperbarui Model dan Template Email: - Mengubah template email untuk mendukung pengisian nama rekening secara dinamis berdasarkan customer account. - Menambahkan pengisian dinamis untuk tahun copyright di footer. - Memperbarui Provider `WebstatementServiceProvider`: - Mendaftarkan Command baru `SendStatementEmailCommand` ke dalam aplikasi. Signed-off-by: Daeng Deni Mardaeni --- app/Console/SendStatementEmailCommand.php | 191 ++++++++++ app/Jobs/SendStatementEmailJob.php | 337 ++++++++++++++++++ app/Mail/StatementEmail.php | 8 +- app/Providers/WebstatementServiceProvider.php | 2 + resources/views/statements/email.blade.php | 136 +++---- 5 files changed, 609 insertions(+), 65 deletions(-) create mode 100644 app/Console/SendStatementEmailCommand.php create mode 100644 app/Jobs/SendStatementEmailJob.php diff --git a/app/Console/SendStatementEmailCommand.php b/app/Console/SendStatementEmailCommand.php new file mode 100644 index 0000000..654db1b --- /dev/null +++ b/app/Console/SendStatementEmailCommand.php @@ -0,0 +1,191 @@ +info('🚀 Memulai proses pengiriman email statement...'); + + try { + // Ambil parameter + $period = $this->argument('period'); + $accountNumber = $this->option('account'); + $batchId = $this->option('batch-id'); + $queueName = $this->option('queue'); + $delay = (int) $this->option('delay'); + + // Validasi parameter + if (!$this->validateParameters($period, $accountNumber)) { + return Command::FAILURE; + } + + // Log command execution + Log::info('SendStatementEmailCommand started', [ + 'period' => $period, + 'account_number' => $accountNumber, + 'batch_id' => $batchId, + 'queue' => $queueName, + 'delay' => $delay + ]); + + // Dispatch job + $job = SendStatementEmailJob::dispatch($period, $accountNumber, $batchId) + ->onQueue($queueName); + + if ($delay > 0) { + $job->delay(now()->addMinutes($delay)); + $this->info("⏰ Job dijadwalkan untuk dijalankan dalam {$delay} menit"); + } + + // Tampilkan informasi + $this->displayJobInfo($period, $accountNumber, $batchId, $queueName); + + $this->info('✅ Job pengiriman email statement berhasil didispatch!'); + $this->info('📊 Gunakan command berikut untuk monitoring:'); + $this->line(" php artisan queue:work {$queueName}"); + $this->line(' php artisan telescope:work (jika menggunakan Telescope)'); + + return Command::SUCCESS; + + } catch (Exception $e) { + $this->error('❌ Error saat mendispatch job: ' . $e->getMessage()); + + Log::error('SendStatementEmailCommand failed', [ + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return Command::FAILURE; + } + } + + /** + * Validasi parameter input + * + * @param string $period + * @param string|null $accountNumber + * @return bool + */ + private function validateParameters($period, $accountNumber = null) + { + // Validasi format periode + if (!preg_match('/^\d{4}\d{2}$/', $period)) { + $this->error('❌ Format periode tidak valid. Gunakan format YYYYMM (contoh: 202401)'); + return false; + } + + // Validasi account number jika diberikan + if ($accountNumber) { + $account = Account::with('customer') + ->where('account_number', $accountNumber) + ->first(); + + if (!$account) { + $this->error("❌ Account {$accountNumber} tidak ditemukan"); + return false; + } + + // Cek apakah ada email (dari stmt_email atau customer email) + $hasEmail = !empty($account->stmt_email) || + ($account->customer && !empty($account->customer->email)); + + if (!$hasEmail) { + $this->error("❌ Account {$accountNumber} tidak memiliki email (baik di stmt_email maupun customer email)"); + return false; + } + + $emailSource = !empty($account->stmt_email) ? 'stmt_email' : 'customer email'; + $emailAddress = !empty($account->stmt_email) ? $account->stmt_email : $account->customer->email; + + $this->info("✅ Account {$accountNumber} ditemukan dengan email: {$emailAddress} (dari {$emailSource}) - Cabang: {$account->branch_code}"); + } else { + // Cek apakah ada account dengan email (dari stmt_email atau customer email) + $accountCount = Account::with('customer') + ->where('stmt_sent_type', 'BY.EMAIL') + ->get() + ->filter(function ($account) { + return !empty($account->stmt_email) || + ($account->customer && !empty($account->customer->email)); + }) + ->count(); + + if ($accountCount === 0) { + $this->error('❌ Tidak ada account dengan email ditemukan (baik di stmt_email maupun customer email)'); + return false; + } + + $this->info("✅ Ditemukan {$accountCount} account dengan email"); + } + + return true; + } + + /** + * Menampilkan informasi job yang akan dijalankan + * + * @param string $period + * @param string|null $accountNumber + * @param string|null $batchId + * @param string $queueName + * @return void + */ + private function displayJobInfo($period, $accountNumber, $batchId, $queueName) + { + $this->info('📋 Detail Job:'); + $this->line(" Periode: {$period}"); + $this->line(" Account: " . ($accountNumber ?: 'Semua account dengan email')); + $this->line(" Batch ID: " . ($batchId ?: 'Auto-generated')); + $this->line(" Queue: {$queueName}"); + + // Estimasi path file + if ($accountNumber) { + $account = Account::where('account_number', $accountNumber)->first(); + if ($account) { + $pdfPath = "storage/app/combine/{$period}/{$account->branch_code}/{$accountNumber}_{$period}.pdf"; + $this->line(" File PDF: {$pdfPath}"); + } + } else { + $this->line(" File PDF: storage/app/combine/{$period}/[branch_code]/[account_number]_{$period}.pdf"); + } + } +} diff --git a/app/Jobs/SendStatementEmailJob.php b/app/Jobs/SendStatementEmailJob.php new file mode 100644 index 0000000..a72e3f4 --- /dev/null +++ b/app/Jobs/SendStatementEmailJob.php @@ -0,0 +1,337 @@ +period = $period; + $this->accountNumber = $accountNumber; + $this->batchId = $batchId ?? uniqid('batch_'); + + Log::info('SendStatementEmailJob created', [ + 'period' => $this->period, + 'account_number' => $this->accountNumber, + 'batch_id' => $this->batchId + ]); + } + + /** + * 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 + ]); + + DB::beginTransaction(); + + try { + // Ambil accounts yang memiliki email + $accounts = $this->getAccountsWithEmail(); + + if ($accounts->isEmpty()) { + Log::warning('No accounts with email found', [ + 'period' => $this->period, + 'account_number' => $this->accountNumber, + 'batch_id' => $this->batchId + ]); + DB::commit(); + return; + } + + $successCount = 0; + $failedCount = 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' => $account->stmt_email, + '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' => $account->stmt_email, + 'error' => $e->getMessage(), + 'batch_id' => $this->batchId + ]); + } + } + + DB::commit(); + + Log::info('SendStatementEmailJob completed', [ + 'batch_id' => $this->batchId, + 'total_accounts' => $accounts->count(), + 'success_count' => $successCount, + 'failed_count' => $failedCount + ]); + + } catch (\Exception $e) { + DB::rollBack(); + + Log::error('SendStatementEmailJob failed', [ + 'batch_id' => $this->batchId, + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + throw $e; + } + } + + /** + * Mengambil accounts yang memiliki email dan sesuai kriteria + * + * @return \Illuminate\Database\Eloquent\Collection + */ + private function getAccountsWithEmail() + { + Log::info('Fetching accounts with email', [ + 'period' => $this->period, + 'account_number' => $this->accountNumber + ]); + + $query = Account::with('customer') + ->where('stmt_sent_type', 'BY.EMAIL'); + + // Jika account number spesifik diberikan + if ($this->accountNumber) { + $query->where('account_number', $this->accountNumber); + } + + // Ambil semua accounts yang memenuhi kriteria + $accounts = $query->get(); + + // Filter accounts yang memiliki email (dari stmt_email atau customer 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; + }); + + Log::info('Accounts with email retrieved', [ + 'total_accounts' => $accounts->count(), + 'accounts_with_email' => $accountsWithEmail->count(), + 'batch_id' => $this->batchId + ]); + + return $accountsWithEmail; + } + + /** + * 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 + 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 + * + * @param \Throwable $exception + * @return void + */ + public function failed(\Throwable $exception) + { + Log::error('SendStatementEmailJob failed permanently', [ + 'batch_id' => $this->batchId, + 'period' => $this->period, + 'account_number' => $this->accountNumber, + 'error' => $exception->getMessage(), + 'trace' => $exception->getTraceAsString() + ]); + } +} diff --git a/app/Mail/StatementEmail.php b/app/Mail/StatementEmail.php index f6cbe5b..ba9b628 100644 --- a/app/Mail/StatementEmail.php +++ b/app/Mail/StatementEmail.php @@ -5,7 +5,8 @@ use Illuminate\Bus\Queueable; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; - use Modules\Webstatement\Models\PrintStatementLog; +use Modules\Webstatement\Models\Account; +use Modules\Webstatement\Models\PrintStatementLog; class StatementEmail extends Mailable { @@ -36,12 +37,12 @@ */ public function build() { - $subject = 'Your Account Statement'; + $subject = 'Statement Rekening Bank Artha Graha Internasional'; if ($this->statement->is_period_range) { $subject .= " - {$this->statement->period_from} to {$this->statement->period_to}"; } else { - $subject .= " - {$this->statement->period_from}"; + $subject .= " - " . \Carbon\Carbon::createFromFormat('Ym', $this->statement->period_from)->locale('id')->isoFormat('MMMM Y'); } $email = $this->subject($subject) @@ -52,6 +53,7 @@ 'periodFrom' => $this->statement->period_from, 'periodTo' => $this->statement->period_to, 'isRange' => $this->statement->is_period_range, + 'accounts' => Account::where('account_number',$this->statement->account_number)->first() ]); if ($this->isZip) { diff --git a/app/Providers/WebstatementServiceProvider.php b/app/Providers/WebstatementServiceProvider.php index a168c92..2e15316 100644 --- a/app/Providers/WebstatementServiceProvider.php +++ b/app/Providers/WebstatementServiceProvider.php @@ -16,6 +16,7 @@ use Modules\Webstatement\Console\GenerateBiayakartuCommand; use Modules\Webstatement\Jobs\UpdateAtmCardBranchCurrencyJob; use Modules\Webstatement\Console\GenerateAtmTransactionReport; use Modules\Webstatement\Console\GenerateBiayaKartuCsvCommand; +use Modules\Webstatement\Console\SendStatementEmailCommand; class WebstatementServiceProvider extends ServiceProvider { @@ -67,6 +68,7 @@ class WebstatementServiceProvider extends ServiceProvider UnlockPdf::class, ExportPeriodStatements::class, GenerateAtmTransactionReport::class, + SendStatementEmailCommand::class ]); } diff --git a/resources/views/statements/email.blade.php b/resources/views/statements/email.blade.php index 76e274e..ae4665c 100644 --- a/resources/views/statements/email.blade.php +++ b/resources/views/statements/email.blade.php @@ -1,5 +1,6 @@ + @@ -59,85 +60,96 @@ } ul.dashed-list { - list-style-type: none; /* Remove default bullet */ - padding-left: 1em; /* Add some left padding for spacing */ + list-style-type: none; + /* Remove default bullet */ + padding-left: 1em; + /* Add some left padding for spacing */ } ul.dashed-list li::before { - content: "– "; /* Use an en dash (U+2013) or a hyphen "-" */ - display: inline-block; /* Ensure proper spacing */ - width: 1em; /* Adjust width as needed */ - margin-left: 0.5em; /* Align the dash properly */ + content: "– "; + /* Use an en dash (U+2013) or a hyphen "-" */ + display: inline-block; + /* Ensure proper spacing */ + width: 1em; + /* Adjust width as needed */ + margin-left: 0.5em; + /* Align the dash properly */ } + -
-
-
- Yang Terhormat Bapak/Ibu Daeng Deni Mardaeni,

+
+
+
+ Yang Terhormat Bapak/Ibu {{ $accounts->customer->name }},

- Terlampir adalah Electronic Statement Rekening Anda.
- Silahkan gunakan password Electronic Statement Anda untuk membukanya.

- Password standar Elektronic Statement ini adalah ddMonyyyyxx (contoh: 01Aug1970xx) dimana : -
    -
  • dd : 2 digit tanggal lahir anda, contoh: 01
  • -
  • Mon : - 3 huruf pertama bulan lahir anda dalam bahasa Ingris. Huruf pertama adalah huruf besar dan selanjutnya huruf kecil, contoh : Aug -
  • -
  • yyyy : 4 digit tahun kelahiran anda, contoh : 1970
  • -
  • xx : 2 digit terakhir dari nomer rekening anda, contoh : 12
  • -
-
+ Terlampir adalah Electronic Statement Rekening Anda.
+ Silahkan gunakan password Electronic Statement Anda untuk membukanya.

+ Password standar Elektronic Statement ini adalah ddMonyyyyxx (contoh: 01Aug1970xx) + dimana : +
    +
  • dd : 2 digit tanggal lahir anda, contoh: 01
  • +
  • Mon : + 3 huruf pertama bulan lahir anda dalam bahasa Ingris. Huruf pertama adalah + huruf besar dan selanjutnya huruf kecil, contoh : Aug +
  • +
  • yyyy : 4 digit tahun kelahiran anda, contoh : 1970
  • +
  • xx : 2 digit terakhir dari nomer rekening anda, contoh : 12
  • +
+
- Terima Kasih,

+ Terima Kasih,

- Bank Artha Graha Internasional
- ------------------------------ - - ------------------------------ - - --------
- Kami sangat menghargai masukan dan saran Anda untuk meningkatkan layanan dan produk kami.
- Untuk memberikan masukan, silakan hubungi GrahaCall 24 Jam kami di - 0-800-191-8880.


+ Bank Artha Graha Internasional
+ ------------------------------ + + ------------------------------ + + --------
+ Kami sangat menghargai masukan dan saran Anda untuk meningkatkan layanan dan produk kami.
+ Untuk memberikan masukan, silakan hubungi GrahaCall 24 Jam kami di + 0-800-191-8880.


- Dear Mr/Mrs/Ms Daeng Deni Mardaeni,

+ Dear Mr/Mrs/Ms {{ $accounts->customer->name }},

- Attached is your Electronic Account Statement.
- Please use your Electronic Statement password to open it.

+ Attached is your Electronic Account Statement.
+ Please use your Electronic Statement password to open it.

- The Electronic Statement standard password is ddMonyyyyxx (example: 01Aug1970xx) where: -
    -
  • dd : The first 2 digits of your birthdate, example: 01
  • -
  • Mon : - The first 3 letters of your birth month in English. The first letter is uppercase and the rest are lowercase, example: Aug -
  • -
  • yyyy : 4 digit of your birth year, example: 1970
  • -
  • xx : The last 2 digits of your account number, example: 12.
  • -
-
+ The Electronic Statement standard password is ddMonyyyyxx (example: 01Aug1970xx) where: +
    +
  • dd : The first 2 digits of your birthdate, example: 01
  • +
  • Mon : + The first 3 letters of your birth month in English. The first letter is + uppercase and the rest are lowercase, example: Aug +
  • +
  • yyyy : 4 digit of your birth year, example: 1970
  • +
  • xx : The last 2 digits of your account number, example: 12.
  • +
+
- Regards,

+ Regards,

- Bank Artha Graha Internasional
- ------------------------------ - - ------------------------------ - - --------
- We welcome any feedback or suggestions to improve our product and services.
- If you have any feedback, please contact our GrahaCall 24 Hours at - 0-800-191-8880. -
-

+ Bank Artha Graha Internasional
+ ------------------------------ + + ------------------------------ + + --------
+ We welcome any feedback or suggestions to improve our product and services.
+ If you have any feedback, please contact our GrahaCall 24 Hours at + 0-800-191-8880. +
+

+
+
- -
+