- **Penambahan Fitur**:
- Menambahkan metode baru `generateSingleAtmCardCsv` untuk membuat file CSV tunggal tanpa pemisahan cabang:
- Mencakup seluruh data kartu ATM yang memenuhi syarat.
- File diunggah ke SFTP tanpa direktori spesifik cabang.
- Implementasi command `UpdateAllAtmCardsCommand` untuk batch update:
- Dukungan konfigurasi parameter seperti batch size, ID log sinkronisasi, queue, filter, dan dry-run.
- **Optimasi Logging**:
- Logging rinci ditambahkan pada semua proses, termasuk:
- Generasi CSV tunggal.
- Proses upload CSV ke SFTP.
- Pembaruan atau pembuatan `KartuSyncLog` dalam batch processing.
- Progress dan status tiap batch.
- Error handling dengan detail informasi pada setiap exception.
- **Perbaikan dan Penyesuaian Job**:
- Penambahan `UpdateAllAtmCardsBatchJob` yang mengatur proses batch update:
- Mendukung operasi batch dengan pengaturan ukuran dan parameter filtering kartu.
- Pencatatan log progres secara dinamis dengan kalkulasi batch dan persentase.
- Menyusun delay antar job untuk performa yang lebih baik.
- Menyertakan validasi untuk sinkronisasi dan pembaruan data kartu ATM.
- **Refaktor Provider**:
- Pendaftaran command baru:
- `UpdateAllAtmCardsCommand` untuk batch update seluruh kartu ATM.
- Command disertakan dalam provider `WebstatementServiceProvider`.
- **Error Handling**:
- Peningkatan mekanisme rollback pada database saat error.
- Menambahkan notifikasi log `failure` apabila job gagal dijalankan.
- **Dokumentasi dan Komentar**:
- Menambahkan komentar mendetail pada setiap fungsi baru untuk penjelasan lebih baik.
- Mendokumentasikan seluruh proses dan perubahan pada job serta command baru terkait kartu ATM.
Perubahan ini meningkatkan efisiensi pengelolaan data kartu ATM, termasuk generasi CSV, proses batch, dan pengunggahan data ke SFTP.
Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
572 lines
20 KiB
PHP
572 lines
20 KiB
PHP
<?php
|
|
|
|
namespace Modules\Webstatement\Jobs;
|
|
|
|
use Carbon\Carbon;
|
|
use Exception;
|
|
use Illuminate\Bus\Queueable;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Bus\Dispatchable;
|
|
use Illuminate\Queue\InteractsWithQueue;
|
|
use Illuminate\Queue\SerializesModels;
|
|
use Illuminate\Support\Facades\Log;
|
|
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
|
|
/**
|
|
* Periode yang akan diproses (YYYY-MM)
|
|
*/
|
|
protected $periode;
|
|
/**
|
|
* ID log sinkronisasi
|
|
*/
|
|
protected $syncLogId;
|
|
/**
|
|
* Model log sinkronisasi
|
|
*/
|
|
protected $syncLog;
|
|
private $csvFilename;
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
|
|
/**
|
|
* Execute the job.
|
|
*/
|
|
public function handle()
|
|
: void
|
|
{
|
|
set_time_limit(self::MAX_EXECUTION_TIME);
|
|
|
|
// Load log sinkronisasi
|
|
$this->syncLog = KartuSyncLog::findOrFail($this->syncLogId->id);
|
|
|
|
|
|
try {
|
|
// Update status CSV generation dimulai
|
|
$this->updateCsvLogStart();
|
|
|
|
// Generate CSV file
|
|
// $result = $this->generateAtmCardCsv();
|
|
|
|
$result = $this->generateSingleAtmCardCsv();
|
|
|
|
// 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'],
|
|
'upload_sftp' => $result['uploadToSftp'] ? 'Berhasil' : 'Gagal',
|
|
'waktu' => $result['timestamp']
|
|
]);
|
|
|
|
if (!$result['uploadToSftp']) {
|
|
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(),
|
|
'periode' => $this->periode
|
|
]);
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* Generate CSV file with ATM card data and upload to SFTP
|
|
*
|
|
* @return array Information about the generated file and upload status
|
|
*/
|
|
private function generateAtmCardCsv(): array
|
|
{
|
|
$cards = $this->getEligibleAtmCards();
|
|
$cardsByBranch = $cards->groupBy('branch');
|
|
$results = [];
|
|
|
|
foreach ($cardsByBranch as $branch => $branchCards) {
|
|
$dateTime = now()->format('Ymd_Hi');
|
|
$branchFilename = pathinfo($this->csvFilename, PATHINFO_FILENAME)
|
|
. '_' . $branch
|
|
. '_' . $dateTime . '.'
|
|
. pathinfo($this->csvFilename, PATHINFO_EXTENSION);
|
|
|
|
$filename = storage_path('app/' . $branchFilename);
|
|
|
|
$handle = fopen($filename, 'w+');
|
|
if (!$handle) {
|
|
throw new RuntimeException("Tidak dapat membuat file CSV: $filename");
|
|
}
|
|
|
|
try {
|
|
foreach ($branchCards as $card) {
|
|
$fee = $this->determineCardFee($card);
|
|
$csvRow = $this->createCsvRow($card, $fee);
|
|
fputcsv($handle, $csvRow, '|');
|
|
}
|
|
} finally {
|
|
fclose($handle);
|
|
}
|
|
|
|
$this->cleanupCsvFile($filename);
|
|
|
|
// Upload file ke SFTP
|
|
$uploadSuccess = $this->uploadToSftpKartu($filename, $card->branch);
|
|
|
|
$results[] = [
|
|
'branch' => $branch,
|
|
'localFilePath' => $filename,
|
|
'recordCount' => count($branchCards),
|
|
'uploadToSftp' => $uploadSuccess,
|
|
'timestamp' => now()->format('Y-m-d H:i:s')
|
|
];
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Get eligible ATM cards from database
|
|
*
|
|
* @return \Illuminate\Database\Eloquent\Collection
|
|
*/
|
|
private function getEligibleAtmCards()
|
|
{
|
|
$cardTypes = array_keys($this->getDefaultFees());
|
|
|
|
return Atmcard::where('crsts', 1)
|
|
->whereNotNull('accflag')
|
|
->where('accflag', '!=', '')
|
|
->where('flag','')
|
|
->whereNotNull('branch')
|
|
->where('branch', '!=', '')
|
|
->whereNotNull('currency')
|
|
->where('currency', '!=', '')
|
|
->whereIn('ctdesc', $cardTypes)
|
|
->whereNotIn('product_code',['6002','6004','6042','6031'])
|
|
->where('branch','!=','ID0019999')
|
|
->get();
|
|
}
|
|
|
|
/**
|
|
* Get default fees from JenisKartu table
|
|
*
|
|
* @return array
|
|
*/
|
|
private function getDefaultFees()
|
|
: array
|
|
{
|
|
return JenisKartu::getDefaultFees();
|
|
}
|
|
|
|
/**
|
|
* Determine fee for a card based on its type
|
|
*
|
|
* @param Atmcard $card
|
|
*
|
|
* @return int
|
|
*/
|
|
private function determineCardFee(Atmcard $card)
|
|
: int
|
|
{
|
|
if ($card->fee) {
|
|
return $card->fee;
|
|
}
|
|
|
|
$defaultFees = $this->getDefaultFees();
|
|
return $defaultFees[$card->ctdesc] ?? 0;
|
|
}
|
|
|
|
/**
|
|
* Create CSV row data for a card
|
|
*
|
|
* @param Atmcard $card
|
|
* @param int $fee
|
|
*
|
|
* @return array
|
|
*/
|
|
private function createCsvRow(Atmcard $card, int $fee)
|
|
: array
|
|
{
|
|
$today = date('Ymd');
|
|
|
|
return [
|
|
'',
|
|
$card->accflag,
|
|
$card->currency ?? 'IDR',
|
|
$fee,
|
|
'PL65129',
|
|
'',
|
|
'',
|
|
$card->branch,
|
|
$today,
|
|
$today,
|
|
'',
|
|
'',
|
|
'ADMIN FEE ATM::' . $card->crdno . '::' . $today,
|
|
'',
|
|
'',
|
|
'',
|
|
'',
|
|
'',
|
|
'',
|
|
'ACAT'
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Remove double quotes from CSV file
|
|
*
|
|
* @param string $filename
|
|
*
|
|
* @return void
|
|
*/
|
|
private function cleanupCsvFile(string $filename)
|
|
: void
|
|
{
|
|
$fileContent = file_get_contents($filename);
|
|
if ($fileContent === false) {
|
|
throw new RuntimeException("Tidak dapat membaca file CSV: $filename");
|
|
}
|
|
|
|
$fileContent = str_replace('"', '', $fileContent);
|
|
|
|
if (file_put_contents($filename, $fileContent) === false) {
|
|
throw new RuntimeException("Tidak dapat menulis ke file CSV: $filename");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Upload the generated CSV file to SFTP server
|
|
*
|
|
* @param string $localFilePath Path to the local CSV file
|
|
*
|
|
* @return bool True if upload successful, false otherwise
|
|
*/
|
|
private function uploadToSftpKartu(string $localFilePath, string $branch = ''): bool
|
|
{
|
|
try {
|
|
// Update status SFTP upload dimulai
|
|
$this->updateSftpLogStart();
|
|
|
|
// Ambil nama file dari path
|
|
$filename = basename($localFilePath);
|
|
|
|
// Ambil konten file
|
|
$fileContent = file_get_contents($localFilePath);
|
|
if ($fileContent === false) {
|
|
Log::error("Tidak dapat membaca file untuk upload: {$localFilePath}");
|
|
return false;
|
|
}
|
|
|
|
// Dapatkan disk SFTP
|
|
$disk = Storage::disk('sftpKartu');
|
|
|
|
// Tentukan path tujuan di server SFTP
|
|
$remotePath = env('BIAYA_KARTU_REMOTE_PATH', '/');
|
|
|
|
// Add branch directory if provided
|
|
if (!empty($branch)) {
|
|
$remotePath = rtrim($remotePath, '/') . '/' . $branch;
|
|
|
|
// Create branch directory if it doesn't exist
|
|
if (!$disk->exists($remotePath)) {
|
|
$disk->makeDirectory($remotePath);
|
|
}
|
|
}
|
|
|
|
$remoteFilePath = rtrim($remotePath, '/') . '/' . $filename;
|
|
|
|
// Upload file ke server SFTP
|
|
$result = $disk->put($remoteFilePath, $fileContent);
|
|
|
|
if ($result) {
|
|
$this->updateSftpLogSuccess($branch);
|
|
Log::info("File CSV biaya kartu ATM untuk cabang {$branch} 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 untuk cabang {$branch} ke SFTP: {$remoteFilePath}");
|
|
return false;
|
|
}
|
|
} catch (Exception $e) {
|
|
$this->updateSftpLogFailed($e->getMessage());
|
|
|
|
Log::error("Error saat mengunggah file ke SFTP untuk cabang {$branch}: " . $e->getMessage(), [
|
|
'file' => $e->getFile(),
|
|
'line' => $e->getLine(),
|
|
'periode' => $this->periode
|
|
]);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private function updateSftpLogSuccess(string $branch = ''): void
|
|
{
|
|
$message = 'File berhasil diupload ke SFTP';
|
|
if (!empty($branch)) {
|
|
$message .= " untuk cabang {$branch}";
|
|
}
|
|
|
|
$this->syncLog->update([
|
|
'is_ftp' => true,
|
|
'ftp_at' => Carbon::now(),
|
|
'ftp_notes' => $message,
|
|
'ftp_destination' => env('SFTP_KARTU_HOST', '/'),
|
|
]);
|
|
|
|
Log::info($message . ' untuk periode ' . $this->periode);
|
|
}
|
|
|
|
/**
|
|
* 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 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);
|
|
}
|
|
|
|
/**
|
|
* Update log saat pembuatan CSV berhasil
|
|
*/
|
|
private function updateCsvLogSuccess($results): void
|
|
{
|
|
foreach ($results as $result) {
|
|
// Get file info
|
|
$fileInfo = pathinfo($result['localFilePath']);
|
|
$fileName = $fileInfo['basename'];
|
|
|
|
$this->syncLog->update([
|
|
'is_csv' => true,
|
|
'csv_at' => Carbon::now(),
|
|
'csv_notes' => 'File CSV untuk cabang ' . $result['branch'] . ' berhasil dibuat: ' . $fileName,
|
|
'file_path' => $result['localFilePath'],
|
|
'file_name' => $fileName,
|
|
]);
|
|
|
|
Log::info('File CSV untuk cabang ' . $result['branch'] . ' 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);
|
|
}
|
|
|
|
/**
|
|
* Generate single CSV file with all ATM card data without branch separation
|
|
*
|
|
* @return array Information about the generated file and upload status
|
|
* @throws RuntimeException
|
|
*/
|
|
private function generateSingleAtmCardCsv(): array
|
|
{
|
|
Log::info('Memulai pembuatan file CSV tunggal untuk semua kartu ATM');
|
|
|
|
try {
|
|
// Ambil semua kartu yang memenuhi syarat
|
|
$cards = $this->getEligibleAtmCards();
|
|
|
|
if ($cards->isEmpty()) {
|
|
Log::warning('Tidak ada kartu ATM yang memenuhi syarat untuk periode ini');
|
|
throw new RuntimeException('Tidak ada kartu ATM yang memenuhi syarat untuk diproses');
|
|
}
|
|
|
|
// Buat nama file dengan timestamp
|
|
$dateTime = now()->format('Ymd_Hi');
|
|
$singleFilename = pathinfo($this->csvFilename, PATHINFO_FILENAME)
|
|
. '_ALL_BRANCHES_'
|
|
. $dateTime . '.'
|
|
. pathinfo($this->csvFilename, PATHINFO_EXTENSION);
|
|
|
|
$filename = storage_path('app/' . $singleFilename);
|
|
|
|
Log::info('Membuat file CSV: ' . $filename);
|
|
|
|
// Buka file untuk menulis
|
|
$handle = fopen($filename, 'w+');
|
|
if (!$handle) {
|
|
throw new RuntimeException("Tidak dapat membuat file CSV: $filename");
|
|
}
|
|
|
|
$recordCount = 0;
|
|
|
|
try {
|
|
// Tulis semua kartu ke dalam satu file
|
|
foreach ($cards as $card) {
|
|
$fee = $this->determineCardFee($card);
|
|
$csvRow = $this->createCsvRow($card, $fee);
|
|
|
|
if (fputcsv($handle, $csvRow, '|') === false) {
|
|
throw new RuntimeException("Gagal menulis data kartu ke file CSV: {$card->crdno}");
|
|
}
|
|
|
|
$recordCount++;
|
|
|
|
// Log progress setiap 1000 record
|
|
if ($recordCount % 1000 === 0) {
|
|
Log::info("Progress: {$recordCount} kartu telah diproses");
|
|
}
|
|
}
|
|
} finally {
|
|
fclose($handle);
|
|
}
|
|
|
|
Log::info("Selesai menulis {$recordCount} kartu ke file CSV");
|
|
|
|
// Bersihkan file CSV (hapus double quotes)
|
|
$this->cleanupCsvFile($filename);
|
|
|
|
Log::info('File CSV berhasil dibersihkan dari double quotes');
|
|
|
|
// Upload file ke SFTP (tanpa branch specific directory)
|
|
$uploadSuccess = true; // $this->uploadSingleFileToSftp($filename);
|
|
|
|
$result = [
|
|
'localFilePath' => $filename,
|
|
'recordCount' => $recordCount,
|
|
'uploadToSftp' => $uploadSuccess,
|
|
'timestamp' => now()->format('Y-m-d H:i:s'),
|
|
'fileName' => $singleFilename
|
|
];
|
|
|
|
Log::info('Pembuatan file CSV tunggal selesai', $result);
|
|
|
|
return $result;
|
|
|
|
} catch (Exception $e) {
|
|
Log::error('Error dalam generateSingleAtmCardCsv: ' . $e->getMessage(), [
|
|
'file' => $e->getFile(),
|
|
'line' => $e->getLine(),
|
|
'trace' => $e->getTraceAsString()
|
|
]);
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Upload single CSV file to SFTP server without branch directory
|
|
*
|
|
* @param string $localFilePath Path to the local CSV file
|
|
* @return bool True if upload successful, false otherwise
|
|
*/
|
|
private function uploadSingleFileToSftp(string $localFilePath): bool
|
|
{
|
|
try {
|
|
Log::info('Memulai upload file tunggal ke SFTP: ' . $localFilePath);
|
|
|
|
// Update status SFTP upload dimulai
|
|
$this->updateSftpLogStart();
|
|
|
|
// Ambil nama file dari path
|
|
$filename = basename($localFilePath);
|
|
|
|
// Ambil konten file
|
|
$fileContent = file_get_contents($localFilePath);
|
|
if ($fileContent === false) {
|
|
Log::error("Tidak dapat membaca file untuk upload: {$localFilePath}");
|
|
return false;
|
|
}
|
|
|
|
// Dapatkan disk SFTP
|
|
$disk = Storage::disk('sftpKartu');
|
|
|
|
// Tentukan path tujuan di server SFTP (root directory)
|
|
$remotePath = env('BIAYA_KARTU_REMOTE_PATH', '/');
|
|
$remoteFilePath = rtrim($remotePath, '/') . '/' . $filename;
|
|
|
|
Log::info('Mengunggah ke path remote: ' . $remoteFilePath);
|
|
|
|
// Upload file ke server SFTP
|
|
$result = $disk->put($remoteFilePath, $fileContent);
|
|
|
|
if ($result) {
|
|
$this->updateSftpLogSuccess();
|
|
Log::info("File CSV tunggal berhasil diunggah ke SFTP: {$remoteFilePath}");
|
|
return true;
|
|
} else {
|
|
$errorMsg = "Gagal mengunggah file CSV tunggal ke SFTP: {$remoteFilePath}";
|
|
$this->updateSftpLogFailed($errorMsg);
|
|
Log::error($errorMsg);
|
|
return false;
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
$errorMsg = "Error saat mengunggah file tunggal ke SFTP: " . $e->getMessage();
|
|
$this->updateSftpLogFailed($errorMsg);
|
|
|
|
Log::error($errorMsg, [
|
|
'file' => $e->getFile(),
|
|
'line' => $e->getLine(),
|
|
'periode' => $this->periode
|
|
]);
|
|
return false;
|
|
}
|
|
}
|
|
}
|