fix(webstatement): perbaiki eliminasi duplicate pada GenerateClosingBalanceReportJob

Mengganti pendekatan eliminasi duplicate dari validasi di level aplikasi menjadi di level database untuk menangani kasus duplikat dengan sequence yang tidak berdekatan.

Perubahan yang dilakukan:
- Mengimplementasikan subquery untuk mendapatkan ID unik berdasarkan kombinasi `trans_reference`, `amount_lcy`, dan `booking_date`
- Menghapus validasi duplicate berbasis array `$seenTransactions` di level aplikasi
- Menggunakan `whereIn` untuk filter transaksi berdasarkan hasil subquery ID unik
- Menyederhanakan method `prepareProcessedClosingBalanceData` karena data sudah bersih dari duplicate
- Menambahkan logging jumlah transaksi unik untuk monitoring
- Mengurangi beban proses sejak awal untuk meningkatkan performa
This commit is contained in:
Daeng Deni Mardaeni
2025-07-31 09:32:14 +07:00
parent 8eb7e69b21
commit bd72eb7dfa

View File

@@ -3,20 +3,18 @@
namespace Modules\Webstatement\Jobs; namespace Modules\Webstatement\Jobs;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\{InteractsWithQueue, SerializesModels};
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Support\Facades\{DB, Log, Storage};
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Carbon\Carbon; use Carbon\Carbon;
use Exception; use Exception;
use Modules\Webstatement\Models\AccountBalance; use Modules\Webstatement\Models\{
use Modules\Webstatement\Models\ClosingBalanceReportLog; AccountBalance,
use Modules\Webstatement\Models\ProcessedClosingBalance; ClosingBalanceReportLog,
use Modules\Webstatement\Models\StmtEntry; ProcessedClosingBalance,
use Modules\Webstatement\Models\StmtEntryDetail; StmtEntry,
StmtEntryDetail
};
/** /**
* Job untuk generate laporan closing balance dengan optimasi performa * Job untuk generate laporan closing balance dengan optimasi performa
@@ -338,12 +336,12 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
* Membangun query transaksi menggunakan relasi Eloquent murni * Membangun query transaksi menggunakan relasi Eloquent murni
*/ */
/** /**
* Build transaction query dengan eliminasi duplicate yang efektif * Build transaction query dengan eliminasi duplicate yang efektif menggunakan subquery
* Membangun query transaksi dengan menghilangkan duplicate trans_reference dan amount_lcy * Membangun query transaksi dengan menghilangkan duplicate trans_reference dan amount_lcy
*/ */
private function buildTransactionQuery() private function buildTransactionQuery()
{ {
Log::info('Building transaction query with duplicate elimination', [ Log::info('Building transaction query with effective duplicate elimination', [
'group_name' => $this->groupName, 'group_name' => $this->groupName,
'account_number' => $this->accountNumber, 'account_number' => $this->accountNumber,
'period' => $this->period 'period' => $this->period
@@ -352,13 +350,25 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
$modelClass = $this->getModelByGroup(); $modelClass = $this->getModelByGroup();
$tableName = (new $modelClass)->getTable(); $tableName = (new $modelClass)->getTable();
// PERBAIKAN: Gunakan groupBy untuk benar-benar menghilangkan duplicate // SOLUSI: Gunakan subquery untuk mendapatkan ID unik dari setiap kombinasi trans_reference + amount_lcy
$uniqueIds = DB::table($tableName)
->select(DB::raw('MIN(id) as min_id'))
->where('account_number', $this->accountNumber)
->where('booking_date', $this->period)
->groupBy('trans_reference', 'amount_lcy', 'booking_date')
->pluck('min_id');
Log::info('Unique transaction IDs identified', [
'total_unique_transactions' => $uniqueIds->count()
]);
// Query hanya transaksi dengan ID yang unik
$query = $modelClass::select([ $query = $modelClass::select([
DB::raw('MIN(id) as id'), // Ambil ID terkecil untuk setiap group 'id',
'trans_reference', 'trans_reference',
'booking_date', 'booking_date',
'amount_lcy', 'amount_lcy',
DB::raw('MIN(date_time) as date_time') // Ambil date_time terkecil untuk konsistensi 'date_time'
]) ])
->with([ ->with([
'ft' => function($query) { 'ft' => function($query) {
@@ -372,44 +382,28 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
$query->select('id', 'date_time'); $query->select('id', 'date_time');
} }
]) ])
->where('account_number', $this->accountNumber) ->whereIn('id', $uniqueIds)
->where('booking_date', $this->period)
// KUNCI: GroupBy untuk menghilangkan duplicate berdasarkan trans_reference dan amount_lcy
->groupBy('trans_reference', 'amount_lcy', 'booking_date')
->orderBy('booking_date') ->orderBy('booking_date')
->orderBy('date_time'); ->orderBy('date_time');
Log::info('Transaction query with duplicate elimination built successfully', [ Log::info('Transaction query with effective duplicate elimination built successfully', [
'model_class' => $modelClass, 'model_class' => $modelClass,
'table_name' => $tableName 'table_name' => $tableName,
'unique_transactions' => $uniqueIds->count()
]); ]);
return $query; return $query;
} }
/** /**
* Prepare processed closing balance data dengan validasi duplicate * Prepare processed closing balance data tanpa validasi duplicate (sudah dieliminasi di query)
* Mempersiapkan data closing balance dengan validasi untuk mencegah duplicate * Mempersiapkan data closing balance tanpa perlu validasi duplicate lagi
*/ */
private function prepareProcessedClosingBalanceData($transactions, &$runningBalance, &$sequenceNo): array private function prepareProcessedClosingBalanceData($transactions, &$runningBalance, &$sequenceNo): array
{ {
$processedData = []; $processedData = [];
$seenTransactions = []; // Track untuk mencegah duplicate di level aplikasi
foreach ($transactions as $transaction) { foreach ($transactions as $transaction) {
// VALIDASI: Cek duplicate di level aplikasi sebagai safety net
$duplicateKey = $transaction->trans_reference . '|' . $transaction->amount_lcy;
if (isset($seenTransactions[$duplicateKey])) {
Log::warning('Duplicate transaction detected and skipped', [
'trans_reference' => $transaction->trans_reference,
'amount_lcy' => $transaction->amount_lcy,
'duplicate_key' => $duplicateKey
]);
continue; // Skip duplicate
}
$seenTransactions[$duplicateKey] = true;
$sequenceNo++; $sequenceNo++;
// Process transaction data // Process transaction data
@@ -458,9 +452,8 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
]; ];
} }
Log::info('Processed closing balance data prepared', [ Log::info('Processed closing balance data prepared without duplicates', [
'total_records' => count($processedData), 'total_records' => count($processedData)
'duplicates_skipped' => count($seenTransactions) - count($processedData)
]); ]);
return $processedData; return $processedData;