diff --git a/app/Jobs/BiayaKartu.php b/app/Jobs/BiayaKartu.php index d591f44..b805a72 100644 --- a/app/Jobs/BiayaKartu.php +++ b/app/Jobs/BiayaKartu.php @@ -10,7 +10,9 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; +use Carbon\Carbon; use Modules\Webstatement\Models\Atmcard; +use Modules\Webstatement\Models\KartuSyncLog; class BiayaKartu implements ShouldQueue { @@ -23,16 +25,133 @@ class BiayaKartu implements ShouldQueue private const BATCH_SIZE = 1000; private const MAX_EXECUTION_TIME = 86400; // 24 jam dalam detik + /** + * Log model untuk menyimpan status sinkronisasi + */ + protected $syncLog; + + /** + * Periode yang sedang disinkronkan (YYYY-MM) + */ + protected $periode; + + /** + * Statistik sinkronisasi + */ + protected $totalRecords = 0; + protected $successRecords = 0; + protected $failedRecords = 0; + + /** + * Create a new job instance. + */ + public function __construct($periode = null) + { + // Jika periode tidak diberikan, gunakan bulan saat ini + $this->periode = $periode ?? Carbon::now()->format('Y-m'); + } + /** * Execute the job. */ public function handle(): void { set_time_limit(self::MAX_EXECUTION_TIME); - $this->syncAtmCards(); - // Setelah sync selesai, jadwalkan job untuk memperbarui branch dan currency - $this->scheduleUpdateBranchCurrencyJobs(); + // Inisialisasi atau ambil log sinkronisasi + $this->initSyncLog(); + + try { + // Tandai sinkronisasi dimulai + $this->updateSyncLogStart(); + + // Proses sinkronisasi + $this->syncAtmCards(); + + // Jadwalkan job untuk update branch dan currency + $this->scheduleUpdateBranchCurrencyJobs(); + + // Tandai sinkronisasi selesai + $this->updateSyncLogSuccess(); + } catch (Exception $e) { + // Catat error + $this->updateSyncLogFailed($e->getMessage()); + + Log::error('BiayaKartu: Sinkronisasi gagal: ' . $e->getMessage(), [ + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'periode' => $this->periode + ]); + } + } + + /** + * Inisialisasi atau ambil log sinkronisasi + */ + private function initSyncLog(): void + { + // Cek apakah sudah ada log untuk periode ini + $this->syncLog = KartuSyncLog::where('periode', $this->periode)->first(); + + // Jika belum ada, buat log baru + if (!$this->syncLog) { + $this->syncLog = KartuSyncLog::create([ + 'periode' => $this->periode, + 'is_sync' => false, + 'is_csv' => false, + 'is_ftp' => false, + 'total_records' => 0, + 'success_records' => 0, + 'failed_records' => 0, + ]); + } + } + + /** + * Update log sinkronisasi saat dimulai + */ + private function updateSyncLogStart(): void + { + $this->syncLog->update([ + 'sync_notes' => 'Sinkronisasi data kartu untuk periode ' . $this->periode . ' dimulai', + 'is_sync' => false, + 'sync_at' => null, + ]); + + Log::info('Memulai sinkronisasi data kartu untuk periode ' . $this->periode); + } + + /** + * Update log sinkronisasi saat berhasil + */ + private function updateSyncLogSuccess(): void + { + $this->syncLog->update([ + 'is_sync' => true, + 'sync_at' => Carbon::now(), + 'sync_notes' => 'Sinkronisasi selesai: ' . $this->totalRecords . ' total, ' + . $this->successRecords . ' berhasil, ' + . $this->failedRecords . ' gagal', + 'total_records' => $this->totalRecords, + 'success_records' => $this->successRecords, + 'failed_records' => $this->failedRecords, + ]); + + Log::info('Sinkronisasi kartu ATM berhasil diselesaikan untuk periode ' . $this->periode); + } + + /** + * Update log sinkronisasi saat gagal + */ + private function updateSyncLogFailed($errorMessage): void + { + $this->syncLog->update([ + 'is_sync' => false, + 'sync_notes' => 'Sinkronisasi gagal: ' . $errorMessage, + 'total_records' => $this->totalRecords, + 'success_records' => $this->successRecords, + 'failed_records' => $this->failedRecords, + ]); } /** @@ -50,16 +169,32 @@ class BiayaKartu implements ShouldQueue $cards = $this->fetchCardBatch($offset); $this->processCardBatch($cards); + // Update total records + $this->totalRecords += count($cards); + + // Update progress di log + if ($offset % 5000 === 0) { + $this->syncLog->update([ + 'sync_notes' => 'Sinkronisasi dalam proses: ' . $this->totalRecords . ' data diproses, ' + . $this->successRecords . ' berhasil, ' + . $this->failedRecords . ' gagal', + 'total_records' => $this->totalRecords, + 'success_records' => $this->successRecords, + 'failed_records' => $this->failedRecords, + ]); + } + $hasMoreRecords = count($cards) === self::BATCH_SIZE; $offset += self::BATCH_SIZE; } - - Log::info('Sinkronisasi kartu ATM berhasil diselesaikan'); } catch (Exception $e) { Log::error('BiayaKartu: syncAtmCards: ' . $e->getMessage(), [ 'file' => $e->getFile(), - 'line' => $e->getLine() + 'line' => $e->getLine(), + 'periode' => $this->periode ]); + + throw $e; } } @@ -98,7 +233,13 @@ class BiayaKartu implements ShouldQueue // Perbarui data kartu dasar $cardData = $this->getCardBaseData($card); Atmcard::updateOrCreate(['crdno' => $card->crdno], $cardData); + + // Tambah hitungan sukses + $this->successRecords++; } catch (Exception $e) { + // Tambah hitungan gagal + $this->failedRecords++; + Log::warning("Gagal memproses kartu {$card->crdno}: " . $e->getMessage()); } } @@ -127,9 +268,14 @@ class BiayaKartu implements ShouldQueue $totalCards = $cards->count(); Log::info("Menjadwalkan {$totalCards} job pembaruan branch dan currency"); + // Update log + $this->syncLog->update([ + 'sync_notes' => $this->syncLog->sync_notes . "\nMenjadwalkan {$totalCards} job pembaruan branch dan currency" + ]); + foreach ($cards as $card) { // Jadwalkan job dengan delay untuk menghindari terlalu banyak request bersamaan - UpdateAtmCardBranchCurrencyJob::dispatch($card) + UpdateAtmCardBranchCurrencyJob::dispatch($card, $this->syncLog->id) ->delay(now()->addSeconds(rand(1, 300))); // Random delay antara 1-300 detik } @@ -139,6 +285,10 @@ class BiayaKartu implements ShouldQueue 'file' => $e->getFile(), 'line' => $e->getLine() ]); + + $this->syncLog->update([ + 'sync_notes' => $this->syncLog->sync_notes . "\nError: Gagal menjadwalkan job pembaruan branch dan currency: " . $e->getMessage() + ]); } } diff --git a/app/Jobs/GenerateBiayaKartuCsvJob.php b/app/Jobs/GenerateBiayaKartuCsvJob.php index 69c52af..018d963 100644 --- a/app/Jobs/GenerateBiayaKartuCsvJob.php +++ b/app/Jobs/GenerateBiayaKartuCsvJob.php @@ -2,6 +2,7 @@ namespace Modules\Webstatement\Jobs; + use Carbon\Carbon; use Exception; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -12,20 +13,41 @@ use Illuminate\Support\Facades\Storage; use Modules\Webstatement\Models\Atmcard; use Modules\Webstatement\Models\JenisKartu; + use Modules\Webstatement\Models\KartuSyncLog; use RuntimeException; class GenerateBiayaKartuCsvJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; // Changed from const to property + private const MAX_EXECUTION_TIME = 3600; // 1 jam dalam detik + private $csvFilename; + /** + * Periode yang akan diproses (YYYY-MM) + */ + protected $periode; + + /** + * ID log sinkronisasi + */ + protected $syncLogId; + + /** + * Model log sinkronisasi + */ + protected $syncLog; + + /** * Create a new job instance. */ public function __construct() { $this->csvFilename = env('BIAYA_KARTU_CSV_FILENAME', 'biaya_kartu_atm.csv'); + $this->periode = $periode ?? Carbon::now()->format('Y-m'); + $this->syncLogId = KartuSyncLog::where('periode', $this->periode)->first(); } /** @@ -45,9 +67,22 @@ public function handle() : void { + set_time_limit(self::MAX_EXECUTION_TIME); + + // Load log sinkronisasi + $this->syncLog = KartuSyncLog::findOrFail($this->syncLogId); + + try { + // Update status CSV generation dimulai + $this->updateCsvLogStart(); + + // Generate CSV file $result = $this->generateAtmCardCsv(); + // Update status CSV generation berhasil + $this->updateCsvLogSuccess($result); + Log::info('Pembuatan dan upload file CSV biaya kartu ATM selesai', [ 'file' => $result['localFilePath'], 'jumlah_kartu' => $result['recordCount'], @@ -59,9 +94,12 @@ Log::warning('File CSV biaya kartu ATM tidak berhasil diunggah ke SFTP, tetapi tersedia secara lokal di: ' . $result['localFilePath']); } } catch (Exception $e) { + $this->updateCsvLogFailed($e->getMessage()); + Log::error('Gagal membuat atau mengunggah file CSV biaya kartu ATM: ' . $e->getMessage(), [ 'file' => $e->getFile(), - 'line' => $e->getLine() + 'line' => $e->getLine(), + 'periode' => $this->periode ]); throw $e; } @@ -222,6 +260,9 @@ : bool { try { + // Update status SFTP upload dimulai + $this->updateSftpLogStart(); + // Ambil nama file dari path $filename = basename($localFilePath); @@ -243,19 +284,116 @@ $result = $disk->put($remoteFilePath, $fileContent); if ($result) { + $this->updateSftpLogSuccess(); Log::info("File CSV biaya kartu ATM berhasil diunggah ke SFTP: {$remoteFilePath}"); return true; } else { + $this->updateSftpLogFailed("Gagal mengunggah file CSV biaya kartu ATM ke SFTP: {$remoteFilePath}"); + Log::error("Gagal mengunggah file CSV biaya kartu ATM ke SFTP: {$remoteFilePath}"); return false; } } catch (Exception $e) { + $this->updateSftpLogFailed($e->getMessage()); + Log::error("Error saat mengunggah file ke SFTP: " . $e->getMessage(), [ 'file' => $e->getFile(), - 'line' => $e->getLine() + 'line' => $e->getLine(), + 'periode' => $this->periode ]); return false; } } + /** + * Update log saat pembuatan CSV dimulai + */ + private function updateCsvLogStart(): void + { + $this->syncLog->update([ + 'csv_notes' => 'Pembuatan file CSV untuk periode ' . $this->periode . ' dimulai', + 'is_csv' => false, + 'csv_at' => null, + ]); + + Log::info('Memulai pembuatan file CSV untuk periode ' . $this->periode); + } + + /** + * Update log saat pembuatan CSV berhasil + */ + private function updateCsvLogSuccess($result): void + { + // Get file info + $fileInfo = pathinfo($result['localFilePath']); + $fileName = $fileInfo['basename']; + $fileSize = Storage::size($result['localFilePath']); + + $this->syncLog->update([ + 'is_csv' => true, + 'csv_at' => Carbon::now(), + 'csv_notes' => 'File CSV berhasil dibuat: ' . $fileName . ' (' . $this->formatSize($fileSize) . ')', + 'file_path' => storage_path('app/' . $result['localFilePath']), + 'file_name' => $fileName, + ]); + + Log::info('File CSV berhasil dibuat: ' . $result['localFilePath']); + } + + /** + * Update log saat pembuatan CSV gagal + */ + private function updateCsvLogFailed($errorMessage): void + { + $this->syncLog->update([ + 'is_csv' => false, + 'csv_notes' => 'Pembuatan file CSV gagal: ' . $errorMessage + ]); + + Log::error('Pembuatan file CSV gagal: ' . $errorMessage); + } + + /** + * Update log saat upload SFTP dimulai + */ + private function updateSftpLogStart(): void + { + $this->syncLog->update([ + 'ftp_notes' => 'Upload ke SFTP untuk file periode ' . $this->periode . ' dimulai', + 'is_ftp' => false, + 'ftp_at' => null, + ]); + + Log::info('Memulai upload ke SFTP untuk periode ' . $this->periode); + } + + /** + * Update log saat upload SFTP berhasil + */ + private function updateSftpLogSuccess(): void + { + $this->syncLog->update([ + 'is_ftp' => true, + 'ftp_at' => Carbon::now(), + 'ftp_notes' => 'File berhasil diupload ke SFTP', + 'ftp_destination' => env('SFTP_KARTU_HOST', '/'), + ]); + + Log::info('File berhasil diupload ke SFTP untuk periode ' . $this->periode); + } + + /** + * Update log saat upload SFTP gagal + */ + private function updateSftpLogFailed($errorMessage): void + { + $this->syncLog->update([ + 'is_ftp' => false, + 'ftp_notes' => 'Upload ke SFTP gagal: ' . $errorMessage + ]); + + Log::error('Upload ke SFTP gagal: ' . $errorMessage); + } + + }