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;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\{InteractsWithQueue, SerializesModels};
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\{DB, Log, Storage};
use Carbon\Carbon;
use Exception;
use Modules\Webstatement\Models\AccountBalance;
use Modules\Webstatement\Models\ClosingBalanceReportLog;
use Modules\Webstatement\Models\ProcessedClosingBalance;
use Modules\Webstatement\Models\StmtEntry;
use Modules\Webstatement\Models\StmtEntryDetail;
use Modules\Webstatement\Models\{
AccountBalance,
ClosingBalanceReportLog,
ProcessedClosingBalance,
StmtEntry,
StmtEntryDetail
};
/**
* Job untuk generate laporan closing balance dengan optimasi performa
@@ -338,12 +336,12 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
* 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
*/
private function buildTransactionQuery()
{
Log::info('Building transaction query with duplicate elimination', [
Log::info('Building transaction query with effective duplicate elimination', [
'group_name' => $this->groupName,
'account_number' => $this->accountNumber,
'period' => $this->period
@@ -352,13 +350,25 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
$modelClass = $this->getModelByGroup();
$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([
DB::raw('MIN(id) as id'), // Ambil ID terkecil untuk setiap group
'id',
'trans_reference',
'booking_date',
'amount_lcy',
DB::raw('MIN(date_time) as date_time') // Ambil date_time terkecil untuk konsistensi
'date_time'
])
->with([
'ft' => function($query) {
@@ -372,44 +382,28 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
$query->select('id', 'date_time');
}
])
->where('account_number', $this->accountNumber)
->where('booking_date', $this->period)
// KUNCI: GroupBy untuk menghilangkan duplicate berdasarkan trans_reference dan amount_lcy
->groupBy('trans_reference', 'amount_lcy', 'booking_date')
->whereIn('id', $uniqueIds)
->orderBy('booking_date')
->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,
'table_name' => $tableName
'table_name' => $tableName,
'unique_transactions' => $uniqueIds->count()
]);
return $query;
}
/**
* Prepare processed closing balance data dengan validasi duplicate
* Mempersiapkan data closing balance dengan validasi untuk mencegah duplicate
* Prepare processed closing balance data tanpa validasi duplicate (sudah dieliminasi di query)
* Mempersiapkan data closing balance tanpa perlu validasi duplicate lagi
*/
private function prepareProcessedClosingBalanceData($transactions, &$runningBalance, &$sequenceNo): array
{
$processedData = [];
$seenTransactions = []; // Track untuk mencegah duplicate di level aplikasi
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++;
// Process transaction data
@@ -458,9 +452,8 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
];
}
Log::info('Processed closing balance data prepared', [
'total_records' => count($processedData),
'duplicates_skipped' => count($seenTransactions) - count($processedData)
Log::info('Processed closing balance data prepared without duplicates', [
'total_records' => count($processedData)
]);
return $processedData;