♻️ refactor(GenerateClosingBalanceReportJob): Sederhanakan penanganan duplikasi sesuai pendekatan ExportStatementJob
- Menambahkan method `updateOrCreate` untuk penanganan duplikasi yang lebih efisien - Menghapus method `deleteExistingProcessedDataWithVerification` dengan verifikasi kompleks - Menghapus method `verifyNoDuplicatesAfterInsert` yang tidak diperlukan lagi - Menghapus method `generateReportData` yang sudah tidak digunakan - Menghapus method `buildReportDataRow` yang menjadi bagian dari method di atas - Menghapus method `exportToCsv` yang sudah tidak relevan - Menyederhanakan query penghapusan data dengan menghapus kondisi `group_name` - Mengubah pendekatan dari insertOrIgnore menjadi updateOrCreate untuk penanganan duplikasi - Menghapus kode yang tidak digunakan untuk mengurangi kompleksitas
This commit is contained in:
@@ -153,7 +153,17 @@
|
|||||||
$processedData = $this->prepareProcessedClosingBalanceData($transactions, $runningBalance, $sequenceNo);
|
$processedData = $this->prepareProcessedClosingBalanceData($transactions, $runningBalance, $sequenceNo);
|
||||||
|
|
||||||
if (!empty($processedData)) {
|
if (!empty($processedData)) {
|
||||||
DB::table('processed_closing_balances')->insert($processedData);
|
foreach ($processedData as $data) {
|
||||||
|
ProcessedClosingBalance::updateOrCreate(
|
||||||
|
[
|
||||||
|
'account_number' => $data['account_number'],
|
||||||
|
'period' => $data['period'],
|
||||||
|
'trans_reference' => $data['trans_reference'],
|
||||||
|
'amount_lcy' => $data['amount_lcy'],
|
||||||
|
],
|
||||||
|
$data
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -178,46 +188,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete existing processed data dengan verifikasi lengkap
|
|
||||||
* Menghapus data processed yang sudah ada dengan verifikasi untuk memastikan tidak ada sisa
|
|
||||||
*/
|
|
||||||
private function deleteExistingProcessedDataWithVerification(array $criteria)
|
|
||||||
: void
|
|
||||||
{
|
|
||||||
Log::info('Deleting existing processed data with verification', $criteria);
|
|
||||||
|
|
||||||
// Count before deletion
|
|
||||||
$beforeCount = ProcessedClosingBalance::where('account_number', $criteria['account_number'])
|
|
||||||
->where('period', $criteria['period'])
|
|
||||||
->where('group_name', $criteria['group_name'])
|
|
||||||
->count();
|
|
||||||
|
|
||||||
// Delete with force
|
|
||||||
$deletedCount = ProcessedClosingBalance::where('account_number', $criteria['account_number'])
|
|
||||||
->where('period', $criteria['period'])
|
|
||||||
->where('group_name', $criteria['group_name'])
|
|
||||||
->delete();
|
|
||||||
|
|
||||||
// Verify deletion
|
|
||||||
$afterCount = ProcessedClosingBalance::where('account_number', $criteria['account_number'])
|
|
||||||
->where('period', $criteria['period'])
|
|
||||||
->where('group_name', $criteria['group_name'])
|
|
||||||
->count();
|
|
||||||
|
|
||||||
if ($afterCount > 0) {
|
|
||||||
throw new Exception("Failed to delete all existing data. Remaining records: {$afterCount}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Log::info('Existing processed data deleted and verified', [
|
|
||||||
'before_count' => $beforeCount,
|
|
||||||
'deleted_count' => $deletedCount,
|
|
||||||
'after_count' => $afterCount,
|
|
||||||
'criteria' => $criteria
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get opening balance from account balance table
|
* Get opening balance from account balance table
|
||||||
* Mengambil saldo awal dari tabel account balance
|
* Mengambil saldo awal dari tabel account balance
|
||||||
@@ -462,58 +432,6 @@
|
|||||||
return $datetime;
|
return $datetime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify no duplicates after insert - simplified version
|
|
||||||
* Memverifikasi tidak ada duplikasi setelah insert data dengan pengecekan yang disederhanakan
|
|
||||||
*/
|
|
||||||
private function verifyNoDuplicatesAfterInsert(array $criteria)
|
|
||||||
: void
|
|
||||||
{
|
|
||||||
Log::info('Verifying no duplicates after insert with simplified check', $criteria);
|
|
||||||
|
|
||||||
// PERBAIKAN: Check for duplicate trans_reference + amount_lcy combinations saja
|
|
||||||
// Karena trans_reference sudah unique secara global, tidak perlu filter by account/period
|
|
||||||
$duplicates = DB::table('processed_closing_balances')
|
|
||||||
->select('trans_reference', 'amount_lcy', DB::raw('COUNT(*) as count'))
|
|
||||||
->where('account_number', $criteria['account_number'])
|
|
||||||
->where('period', $criteria['period'])
|
|
||||||
->where('group_name', $criteria['group_name'])
|
|
||||||
->groupBy('trans_reference', 'amount_lcy')
|
|
||||||
->having('count', '>', 1)
|
|
||||||
->get();
|
|
||||||
|
|
||||||
if ($duplicates->count() > 0) {
|
|
||||||
Log::error('Duplicates found after insert', [
|
|
||||||
'duplicate_count' => $duplicates->count(),
|
|
||||||
'duplicates' => $duplicates->toArray()
|
|
||||||
]);
|
|
||||||
|
|
||||||
throw new Exception("Duplicates detected after insert: {$duplicates->count()} duplicate combinations found");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for duplicate sequence numbers dalam scope yang sama
|
|
||||||
$sequenceDuplicates = DB::table('processed_closing_balances')
|
|
||||||
->select('sequence_no', DB::raw('COUNT(*) as count'))
|
|
||||||
->where('account_number', $criteria['account_number'])
|
|
||||||
->where('period', $criteria['period'])
|
|
||||||
->where('group_name', $criteria['group_name'])
|
|
||||||
->groupBy('sequence_no')
|
|
||||||
->having('count', '>', 1)
|
|
||||||
->get();
|
|
||||||
|
|
||||||
if ($sequenceDuplicates->count() > 0) {
|
|
||||||
Log::error('Sequence number duplicates found after insert', [
|
|
||||||
'sequence_duplicate_count' => $sequenceDuplicates->count(),
|
|
||||||
'sequence_duplicates' => $sequenceDuplicates->toArray()
|
|
||||||
]);
|
|
||||||
|
|
||||||
throw new Exception("Sequence number duplicates detected: {$sequenceDuplicates->count()} duplicate sequences found");
|
|
||||||
}
|
|
||||||
|
|
||||||
Log::info('No duplicates found after insert - verification passed', $criteria);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export from database to CSV (very fast)
|
* Export from database to CSV (very fast)
|
||||||
*/
|
*/
|
||||||
@@ -676,7 +594,6 @@
|
|||||||
|
|
||||||
$deletedCount = ProcessedClosingBalance::where('account_number', $criteria['account_number'])
|
$deletedCount = ProcessedClosingBalance::where('account_number', $criteria['account_number'])
|
||||||
->where('period', $criteria['period'])
|
->where('period', $criteria['period'])
|
||||||
->where('group_name', $criteria['group_name'])
|
|
||||||
->delete();
|
->delete();
|
||||||
|
|
||||||
Log::info('Existing processed data deleted', [
|
Log::info('Existing processed data deleted', [
|
||||||
@@ -684,237 +601,4 @@
|
|||||||
'criteria' => $criteria
|
'criteria' => $criteria
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updated generateReportData method using pure ORM
|
|
||||||
* Method generateReportData yang diperbarui menggunakan ORM murni
|
|
||||||
*/
|
|
||||||
private function generateReportData()
|
|
||||||
: array
|
|
||||||
{
|
|
||||||
Log::info('Starting report data generation using pure ORM', [
|
|
||||||
'account_number' => $this->accountNumber,
|
|
||||||
'period' => $this->period,
|
|
||||||
'group_name' => $this->groupName,
|
|
||||||
'chunk_size' => $this->chunkSize
|
|
||||||
]);
|
|
||||||
|
|
||||||
$reportData = [];
|
|
||||||
$runningBalance = $this->getOpeningBalance();
|
|
||||||
$sequenceNo = 1;
|
|
||||||
|
|
||||||
try {
|
|
||||||
DB::beginTransaction();
|
|
||||||
|
|
||||||
// Build query menggunakan pure ORM
|
|
||||||
$query = $this->buildTransactionQuery();
|
|
||||||
|
|
||||||
// Process data dalam chunks untuk efisiensi memory
|
|
||||||
$query->chunk($this->chunkSize, function ($transactions) use (&$reportData, &$runningBalance, &$sequenceNo) {
|
|
||||||
Log::info('Processing transaction chunk', [
|
|
||||||
'chunk_size' => $transactions->count(),
|
|
||||||
'current_sequence' => $sequenceNo,
|
|
||||||
'current_balance' => $runningBalance
|
|
||||||
]);
|
|
||||||
|
|
||||||
foreach ($transactions as $transaction) {
|
|
||||||
// Process transaction data
|
|
||||||
$processedData = $this->processTransactionData($transaction);
|
|
||||||
|
|
||||||
// Update running balance
|
|
||||||
$amount = (float) $transaction->amount_lcy;
|
|
||||||
$runningBalance += $amount;
|
|
||||||
|
|
||||||
// Format transaction date
|
|
||||||
$transactionDate = $this->formatDateTime($processedData['date_time']);
|
|
||||||
|
|
||||||
// Build report data row
|
|
||||||
$reportData[] = $this->buildReportDataRow(
|
|
||||||
(object) $processedData,
|
|
||||||
$sequenceNo,
|
|
||||||
$transactionDate,
|
|
||||||
$runningBalance
|
|
||||||
);
|
|
||||||
|
|
||||||
$sequenceNo++;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log::info('Chunk processed successfully', [
|
|
||||||
'processed_count' => $transactions->count(),
|
|
||||||
'total_records_so_far' => count($reportData),
|
|
||||||
'current_balance' => $runningBalance
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
DB::commit();
|
|
||||||
|
|
||||||
Log::info('Report data generation completed using pure ORM', [
|
|
||||||
'total_records' => count($reportData),
|
|
||||||
'final_balance' => $runningBalance,
|
|
||||||
'final_sequence' => $sequenceNo - 1
|
|
||||||
]);
|
|
||||||
|
|
||||||
} catch (Exception $e) {
|
|
||||||
DB::rollback();
|
|
||||||
|
|
||||||
Log::error('Error generating report data using pure ORM', [
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
'trace' => $e->getTraceAsString(),
|
|
||||||
'account_number' => $this->accountNumber,
|
|
||||||
'period' => $this->period
|
|
||||||
]);
|
|
||||||
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $reportData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build report data row from transaction
|
|
||||||
* Membangun baris data laporan dari transaksi
|
|
||||||
*/
|
|
||||||
private function buildReportDataRow($transaction, int $sequenceNo, string $transactionDate, float $runningBalance)
|
|
||||||
: array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'sequence_no' => $sequenceNo,
|
|
||||||
'trans_reference' => $transaction->trans_reference,
|
|
||||||
'booking_date' => $transaction->booking_date,
|
|
||||||
'transaction_date' => $transactionDate,
|
|
||||||
'amount_lcy' => $transaction->amount_lcy,
|
|
||||||
'debit_acct_no' => $transaction->debit_acct_no,
|
|
||||||
'debit_value_date' => $transaction->debit_value_date,
|
|
||||||
'debit_amount' => $transaction->debit_amount,
|
|
||||||
'credit_acct_no' => $transaction->credit_acct_no,
|
|
||||||
'bif_rcv_acct' => $transaction->bif_rcv_acct,
|
|
||||||
'bif_rcv_name' => $transaction->bif_rcv_name,
|
|
||||||
'credit_value_date' => $transaction->credit_value_date,
|
|
||||||
'credit_amount' => $transaction->credit_amount,
|
|
||||||
'at_unique_id' => $transaction->at_unique_id,
|
|
||||||
'bif_ref_no' => $transaction->bif_ref_no,
|
|
||||||
'atm_order_id' => $transaction->atm_order_id,
|
|
||||||
'recipt_no' => $transaction->recipt_no,
|
|
||||||
'api_iss_acct' => $transaction->api_iss_acct,
|
|
||||||
'api_benff_acct' => $transaction->api_benff_acct,
|
|
||||||
'authoriser' => $transaction->authoriser,
|
|
||||||
'remarks' => $transaction->remarks,
|
|
||||||
'payment_details' => $transaction->payment_details,
|
|
||||||
'ref_no' => $transaction->ref_no,
|
|
||||||
'merchant_id' => $transaction->merchant_id,
|
|
||||||
'term_id' => $transaction->term_id,
|
|
||||||
'closing_balance' => $runningBalance
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Export report data to CSV file
|
|
||||||
* Export data laporan ke file CSV
|
|
||||||
*/
|
|
||||||
private function exportToCsv(array $reportData)
|
|
||||||
: string
|
|
||||||
{
|
|
||||||
Log::info('Starting CSV export for closing balance report', [
|
|
||||||
'account_number' => $this->accountNumber,
|
|
||||||
'period' => $this->period,
|
|
||||||
'record_count' => count($reportData)
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Create directory structure
|
|
||||||
$basePath = "closing_balance_reports";
|
|
||||||
$accountPath = "{$basePath}/{$this->accountNumber}";
|
|
||||||
|
|
||||||
Storage::disk($this->disk)->makeDirectory($basePath);
|
|
||||||
Storage::disk($this->disk)->makeDirectory($accountPath);
|
|
||||||
|
|
||||||
// Generate filename
|
|
||||||
$fileName = "closing_balance_{$this->accountNumber}_{$this->period}.csv";
|
|
||||||
$filePath = "{$accountPath}/{$fileName}";
|
|
||||||
|
|
||||||
// Delete existing file if exists
|
|
||||||
if (Storage::disk($this->disk)->exists($filePath)) {
|
|
||||||
Storage::disk($this->disk)->delete($filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create CSV header
|
|
||||||
$csvHeader = [
|
|
||||||
'NO',
|
|
||||||
'TRANS_REFERENCE',
|
|
||||||
'BOOKING_DATE',
|
|
||||||
'TRANSACTION_DATE',
|
|
||||||
'AMOUNT_LCY',
|
|
||||||
'DEBIT_ACCT_NO',
|
|
||||||
'DEBIT_VALUE_DATE',
|
|
||||||
'DEBIT_AMOUNT',
|
|
||||||
'CREDIT_ACCT_NO',
|
|
||||||
'BIF_RCV_ACCT',
|
|
||||||
'BIF_RCV_NAME',
|
|
||||||
'CREDIT_VALUE_DATE',
|
|
||||||
'CREDIT_AMOUNT',
|
|
||||||
'AT_UNIQUE_ID',
|
|
||||||
'BIF_REF_NO',
|
|
||||||
'ATM_ORDER_ID',
|
|
||||||
'RECIPT_NO',
|
|
||||||
'API_ISS_ACCT',
|
|
||||||
'API_BENFF_ACCT',
|
|
||||||
'AUTHORISER',
|
|
||||||
'REMARKS',
|
|
||||||
'PAYMENT_DETAILS',
|
|
||||||
'REF_NO',
|
|
||||||
'MERCHANT_ID',
|
|
||||||
'TERM_ID',
|
|
||||||
'CLOSING_BALANCE'
|
|
||||||
];
|
|
||||||
|
|
||||||
$csvContent = implode('|', $csvHeader) . "\n";
|
|
||||||
|
|
||||||
// Add data rows
|
|
||||||
foreach ($reportData as $row) {
|
|
||||||
$csvRow = [
|
|
||||||
$row['sequence_no'],
|
|
||||||
$row['trans_reference'] ?? '',
|
|
||||||
$row['booking_date'] ?? '',
|
|
||||||
$row['transaction_date'] ?? '',
|
|
||||||
$row['amount_lcy'] ?? '',
|
|
||||||
$row['debit_acct_no'] ?? '',
|
|
||||||
$row['debit_value_date'] ?? '',
|
|
||||||
$row['debit_amount'] ?? '',
|
|
||||||
$row['credit_acct_no'] ?? '',
|
|
||||||
$row['bif_rcv_acct'] ?? '',
|
|
||||||
$row['bif_rcv_name'] ?? '',
|
|
||||||
$row['credit_value_date'] ?? '',
|
|
||||||
$row['credit_amount'] ?? '',
|
|
||||||
$row['at_unique_id'] ?? '',
|
|
||||||
$row['bif_ref_no'] ?? '',
|
|
||||||
$row['atm_order_id'] ?? '',
|
|
||||||
$row['recipt_no'] ?? '',
|
|
||||||
$row['api_iss_acct'] ?? '',
|
|
||||||
$row['api_benff_acct'] ?? '',
|
|
||||||
$row['authoriser'] ?? '',
|
|
||||||
$row['remarks'] ?? '',
|
|
||||||
$row['payment_details'] ?? '',
|
|
||||||
$row['ref_no'] ?? '',
|
|
||||||
$row['merchant_id'] ?? '',
|
|
||||||
$row['term_id'] ?? '',
|
|
||||||
$row['closing_balance'] ?? ''
|
|
||||||
];
|
|
||||||
|
|
||||||
$csvContent .= implode('|', $csvRow) . "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save file
|
|
||||||
Storage::disk($this->disk)->put($filePath, $csvContent);
|
|
||||||
|
|
||||||
// Verify file creation
|
|
||||||
if (!Storage::disk($this->disk)->exists($filePath)) {
|
|
||||||
throw new Exception("Failed to create CSV file: {$filePath}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Log::info('CSV export completed successfully', [
|
|
||||||
'file_path' => $filePath,
|
|
||||||
'file_size' => Storage::disk($this->disk)->size($filePath)
|
|
||||||
]);
|
|
||||||
|
|
||||||
return $filePath;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user