feat(closing-balance): implementasi unique_hash dan insertOrIgnore untuk eliminasi duplikasi

Perbaikan masalah duplikasi pada laporan penutupan saldo dengan pendekatan hash unik dan query insert yang toleran terhadap duplikasi.

Perubahan:
- Tambah kolom `unique_hash` pada tabel `processed_closing_balances` (via migrasi `2025_07_31_035159_add_unique_hash_field_to_processed_closing_balances_table.php`)
- Tambah field `unique_hash` ke `$fillable` pada model `ProcessedClosingBalance`
- Update logika generate unique key di `prepareProcessedClosingBalanceData()` menggunakan `md5(trans_reference + '_' + amount_lcy)`
- Query pencarian duplikasi berdasarkan `unique_hash`, bukan `trans_reference` saja
- Ganti `insert()` dengan `insertOrIgnore()` untuk mencegah error saat insert duplikat data

Dampak:
- Duplikasi data dihindari secara efektif lewat hash unik
- Tidak ada error meski data duplicate ditemukan, karena query otomatis mengabaikannya
- `trans_reference` yang sama tetap valid selama nilai `amount_lcy` berbeda
- Data laporan lebih konsisten dan terhindar dari konflik constraint
This commit is contained in:
Daeng Deni Mardaeni
2025-07-31 11:06:11 +07:00
parent 13e077073b
commit 710cbb5232
3 changed files with 37 additions and 25 deletions

View File

@@ -177,7 +177,8 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
$insertChunks = array_chunk($allProcessedData, 1000); $insertChunks = array_chunk($allProcessedData, 1000);
foreach ($insertChunks as $chunk) { foreach ($insertChunks as $chunk) {
DB::table('processed_closing_balances')->insert($chunk); // Menggunakan insertOrIgnore untuk mengabaikan duplikasi dan mencegah error unique constraint
DB::table('processed_closing_balances')->insertOrIgnore($chunk);
} }
Log::info('All processed data inserted successfully', [ Log::info('All processed data inserted successfully', [
@@ -527,35 +528,17 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
private function prepareProcessedClosingBalanceData($transactions, &$runningBalance, &$sequenceNo): array private function prepareProcessedClosingBalanceData($transactions, &$runningBalance, &$sequenceNo): array
{ {
$processedData = []; $processedData = [];
$seenReferences = [];
// Buat lookup array untuk validasi cepat - periksa kombinasi trans_reference + amount_lcy
$existingReferences = ProcessedClosingBalance::where('account_number', $this->accountNumber)
->where('period', $this->period)
->where('group_name', $this->groupName)
->get(['trans_reference', 'amount_lcy'])
->map(function ($item) {
return md5($item->trans_reference . '_' . $item->amount_lcy);
})
->toArray();
// Buat lookup array untuk validasi cepat
$existingReferences = array_flip($existingReferences);
foreach ($transactions as $transaction) { foreach ($transactions as $transaction) {
// Validasi duplikasi berbasis trans_reference + amount_lcy + booking_date // Validasi duplikasi berbasis trans_reference + amount_lcy + booking_date
$uniqueKey = md5($transaction->trans_reference . '_' . $transaction->amount_lcy); $uniqueKey = md5($transaction->trans_reference . '_' . $transaction->amount_lcy);
if (isset($seenReferences[$uniqueKey])) { $existingReferences = ProcessedClosingBalance::select('unique_hash')
Log::warning('Duplicate transaction skipped', [ ->where('unique_hash', $uniqueKey)
'trans_reference' => $transaction->trans_reference, ->get();
'amount_lcy' => $transaction->amount_lcy
]);
continue;
}
// Periksa kombinasi trans_reference + amount_lcy, bukan hanya trans_reference // Periksa kombinasi trans_reference + amount_lcy, bukan hanya trans_reference
if (isset($existingReferences[$uniqueKey])) { if ($existingReferences->count() > 0) {
Log::warning('Transaction already exists in database', [ Log::warning('Transaction already exists in database', [
'trans_reference' => $transaction->trans_reference, 'trans_reference' => $transaction->trans_reference,
'amount_lcy' => $transaction->amount_lcy 'amount_lcy' => $transaction->amount_lcy
@@ -610,7 +593,7 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
'created_at' => now(), 'created_at' => now(),
'updated_at' => now(), 'updated_at' => now(),
// Tambahkan hash unik untuk memastikan keunikan // Tambahkan hash unik untuk memastikan keunikan
'unique_hash' => md5($this->accountNumber . $this->period . $this->groupName . $transaction->trans_reference . $transaction->amount_lcy . $transaction->booking_date) 'unique_hash' => md5($transaction->trans_reference . '_' . $transaction->amount_lcy)
]; ];
} }

View File

@@ -35,7 +35,8 @@ class ProcessedClosingBalance extends Model
'ref_no', 'ref_no',
'merchant_id', 'merchant_id',
'term_id', 'term_id',
'closing_balance' 'closing_balance',
'unique_hash',
]; ];
protected $casts = [ protected $casts = [

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('processed_closing_balances', function (Blueprint $table) {
$table->string('unique_hash')->after('id')->unique();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('processed_closing_balances', function (Blueprint $table) {
$table->dropColumn('unique_hash');
});
}
};