periode = $periode ?? Carbon::now()->format('Y-m'); } /** * Execute the job. */ public function handle() : void { set_time_limit(self::MAX_EXECUTION_TIME); // 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); } /** * Synchronize ATM cards data from Oracle database * * @return void */ private function syncAtmCards() : void { try { $offset = 0; $hasMoreRecords = true; while ($hasMoreRecords) { $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; } } catch (Exception $e) { Log::error('BiayaKartu: syncAtmCards: ' . $e->getMessage(), [ 'file' => $e->getFile(), 'line' => $e->getLine(), 'periode' => $this->periode ]); throw $e; } } /** * Fetch a batch of ATM cards from the database * * @param int $offset * * @return \Illuminate\Support\Collection */ private function fetchCardBatch(int $offset) { return DB::connection('oracle') ->table(self::DB_TABLE) ->select('CRDNO', 'ACCFLAG', 'CRACC1', 'CRACC2', 'CRACC3', 'CRACC4', 'CRACC5', 'CRACCNAM1', 'CRACCNAM2', 'CRACCNAM3', 'CRACCNAM4', 'CRACCNAM5', 'CRSTS', 'CTTYPE', 'CTDESC', 'CRDATE', 'LASTUPDATE') ->join('IST77.CMS_VCARDTYP', 'IST77.CMS_VCARD.CRTYPE', '=', 'IST77.CMS_VCARDTYP.CTTYPE') ->where('crsts', '!=',2) ->whereNotIn('cttype',['2','3','6']) ->whereNotNull('ACCFLAG') ->where('ACCFLAG', '>', 0) ->skip($offset) ->take(self::BATCH_SIZE) ->get(); } /** * Process a batch of ATM cards * * @param \Illuminate\Support\Collection $cards * * @return void */ private function processCardBatch($cards) : void { foreach ($cards as $card) { try { // 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()); } } } /** * Get base data for card update * * @param object $card * * @return array */ private function getCardBaseData(object $card) : array { return [ 'accflag' => $card->accflag, 'cracc1' => $card->cracc1, 'cracc2' => $card->cracc2, 'cracc3' => $card->cracc3, 'cracc4' => $card->cracc4, 'cracc5' => $card->cracc5, 'craccnam1' => $card->craccnam1, 'craccnam2' => $card->craccnam2, 'craccnam3' => $card->craccnam3, 'craccnam4' => $card->craccnam4, 'craccnam5' => $card->craccnam5, 'crsts' => $card->crsts, 'cttype' => $card->cttype, 'ctdesc' => $card->ctdesc, 'crdate' => $card->crdate, 'last_update' => $card->lastupdate, ]; } /** * Schedule update branch and currency jobs for cards that need update * * @return void */ /** * Schedule update branch and currency jobs for cards that need update * * @return void */ private function scheduleUpdateBranchCurrencyJobs(): void { try { // Process cards in chunks to avoid memory issues $batchSize = 1000; // Process 1000 cards at a time $totalProcessed = 0; $batchNumber = 0; // Get total count first (without loading all records) $totalCards = Atmcard::where('crsts', 1) ->whereNotNull('accflag') ->where('accflag', '!=', '') ->where('flag','') ->where(function ($query) { $query->whereNull('branch') ->orWhere('branch', '') ->orWhereNull('currency') ->orWhere('currency', ''); }) ->count(); Log::info("Total {$totalCards} cards need branch and currency update"); // Update log $this->syncLog->update([ 'sync_notes' => $this->syncLog->sync_notes . "\nTotal {$totalCards} cards need branch and currency update" ]); // Process in batches do { // Get a batch of cards $cards = Atmcard::where('crsts', 1) ->whereNotNull('accflag') ->where('accflag', '!=', '') ->where('flag','') ->where(function ($query) { $query->whereNull('branch') ->orWhere('branch', '') ->orWhereNull('currency') ->orWhere('currency', ''); }) ->skip($batchNumber * $batchSize) ->take($batchSize) ->get(); $currentBatchCount = $cards->count(); $totalProcessed += $currentBatchCount; if ($currentBatchCount > 0) { Log::info("Processing batch #{$batchNumber}: {$currentBatchCount} cards (Total processed: {$totalProcessed}/{$totalCards})"); // Schedule jobs for this batch with increasing delay foreach ($cards as $index => $card) { // Calculate delay based on batch number and index to spread out job execution $delay = ($batchNumber * $batchSize + $index) % 300; // Spread over 5 minutes (300 seconds) // Dispatch job with the card ID instead of the full object to save memory UpdateAtmCardBranchCurrencyJob::dispatch($card, $this->syncLog->id) ->delay(now()->addSeconds($delay)); } // Update progress in log every 10 batches if ($batchNumber % 10 === 0) { $this->syncLog->update([ 'sync_notes' => $this->syncLog->sync_notes . "\nProcessed {$totalProcessed} of {$totalCards} cards for branch and currency update" ]); } } $batchNumber++; // Free up memory unset($cards); // Force garbage collection if PHP version supports it if (function_exists('gc_collect_cycles')) { gc_collect_cycles(); } } while ($currentBatchCount > 0); Log::info("All {$totalProcessed} branch and currency update jobs have been scheduled"); // Final update to log $this->syncLog->update([ 'sync_notes' => $this->syncLog->sync_notes . "\nAll {$totalProcessed} branch and currency update jobs have been scheduled" ]); } catch (Exception $e) { Log::error('Failed to schedule branch and currency update jobs: ' . $e->getMessage(), [ 'file' => $e->getFile(), 'line' => $e->getLine() ]); $this->syncLog->update([ 'sync_notes' => $this->syncLog->sync_notes . "\nError: Failed to schedule branch and currency update jobs: " . $e->getMessage() ]); } } /** * 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, ]); } }