Files
webstatement/app/Jobs/GenerateBiayaKartuCsvJob.php
Daeng Deni Mardaeni 5d0dbfcf21 🔄 refactor(jobs): perbaikan logika pada beberapa controller dan job
- **WebstatementController.php**:
  - Menyederhanakan fungsi `index()` dengan mengubah parameter menjadi langsung `string $queueName='default'`.
  - Menghapus pengambilan parameter `$queueName` dari objek `Request`.
- **ExportStatementPeriodJob.php**:
  - Memperbaiki perhitungan saldo berjalan (`running balance`) dengan mempertimbangkan mata uang.
  - Menambahkan logika penggunaan `amount_fcy` jika mata uang bukan IDR.
  - Menyesuaikan tipe transaksi (D/C) menggunakan nilai `amount` yang telah disesuaikan.
- **GenerateBiayaKartuCsvJob.php**:
  - Mengubah daftar produk yang dikecualikan menjadi `['6031','6021','6042']`.
  - Memperbaiki filter khusus dengan mengecualikan `product_code` 6004 jika `ctdesc` = CLASSIC.
  - Menambahkan kolom hash unik 16 digit pada data CSV untuk identifikasi setiap record.
- **ProcessCustomerDataJob.php**:
  - Menambahkan mapping baru `name_1` → `name` pada `getHeaderMapping`.
  - Menambahkan logging untuk field `fillable` agar debugging lebih mudah.
2025-09-09 08:51:53 +07:00

601 lines
21 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
* Mengambil data kartu ATM yang memenuhi syarat untuk dikenakan biaya admin
* dengan filter khusus untuk mengecualikan product_code 6021 yang ctdesc nya gold
*
* @return \Illuminate\Database\Eloquent\Collection
*/
private function getEligibleAtmCards()
{
// Log: Memulai proses pengambilan data kartu ATM yang eligible
Log::info('Starting to fetch eligible ATM cards', [
'periode' => $this->periode
]);
$cardTypes = array_keys($this->getDefaultFees());
$query = Atmcard::where('crsts', 1)
->whereNotNull('accflag')
->where('accflag', '!=', '')
->where('flag','')
->whereNotNull('branch')
->where('branch', '!=', '')
->whereNotNull('currency')
->where('currency', '!=', '')
->whereIn('ctdesc', $cardTypes)
->whereNotIn('product_code',['6031','6021','6042']) // Hapus 6021 dari sini
->where('branch','!=','ID0019999')
->where(function($query) {
$query->whereNot(function($q) {
$q->where('product_code', '6004')
->where('ctdesc', 'CLASSIC');
});
});
$cards = $query->get();
// Log: Hasil pengambilan data kartu ATM
Log::info('Eligible ATM cards fetched successfully', [
'total_cards' => $cards->count(),
'periode' => $this->periode,
'excluded_product_codes' => ['6021','6042','6031'],
'special_filter' => 'product_code 6004 dengan ctdesc classic dikecualikan'
]);
return $cards;
}
/**
* 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');
// Generate hash string unik 16 digit
$uniqueHash = substr(hash('sha256', $card->crdno . $today . microtime(true) . uniqid()), 0, 16);
return [
'',
$card->accflag,
$card->currency ?? 'IDR',
$fee,
'PL65129',
'',
'',
$card->branch,
$today,
$today,
'',
'',
'ADMIN FEE ATM::' . $card->crdno . '::' . $today,
'',
'',
'',
'',
'',
'',
'ACAT',
$uniqueHash
];
}
/**
* 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;
}
}
}