feat(webstatement): tambah optimasi pemrosesan multi_account dan validasi statement
Perubahan yang dilakukan: - Memodifikasi PrintStatementController untuk mendukung request_type baru: multi_account. - Menambahkan validasi stmt_sent_type dan branch_code khusus pada request multi_account. - Menambahkan pengecekan branch_id: ID0019999 dengan penanganan error yang lebih spesifik. - Menambahkan metode processMultiAccountStatement untuk pemrosesan berdasarkan branch_code dan stmt_sent_type. Optimasi PDF: - Melakukan refaktor pada GenerateMultiAccountPdfJob agar mendukung kalkulasi tanggal dinamis (startDate dan endDate). - Mengimplementasikan Browsershot untuk opsi tambahan background dan optimasi waktu proses. - Menambahkan validasi status dan update log pada PrintStatementLog setelah PDF berhasil dibuat. - Menambahkan penanganan penggunaan memori secara granular untuk proses batch PDF dan pembersihan resource otomatis. Logging dan Validasi: - Menambahkan logging pada proses kalkulasi tanggal multi_account. - Logging tambahan dan rollback untuk error yang terjadi saat proses statement atau PDF. - Mengubah penggunaan Auth:: untuk konsistensi role checking. - Mengubah validasi stmt_sent_type dari JSON menjadi array dengan implode(). UI dan Output: - Memodifikasi blade template agar mendukung tampilan stmt_sent_type untuk kasus multi_account. - Menambahkan logika kolom dinamis berdasarkan account_number atau stmt_sent_type. Refaktor umum: - Memisahkan logika antara single dan multi account di PrintStatementController. - Perbaikan minor pada query SQL untuk entri ProcessedStatement. Tujuan perubahan: - Mendukung pemrosesan batch statement multi account secara lebih efisien dan terstruktur. - Menjamin validasi dan logging yang lebih kuat. - Meningkatkan performa pembuatan PDF dan kontrol terhadap penggunaan resource. Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
This commit is contained in:
@@ -45,33 +45,45 @@ ini_set('max_execution_time', 300000);
|
||||
// Add account verification before storing
|
||||
$accountNumber = $request->input('account_number'); // Assuming this is the field name for account number
|
||||
// First, check if the account exists and get branch information
|
||||
$account = Account::where('account_number', $accountNumber)->first();
|
||||
if ($account) {
|
||||
$branch_code = $account->branch_code;
|
||||
$userBranchId = session('branch_id'); // Assuming branch ID is stored in session
|
||||
$multiBranch = session('MULTI_BRANCH');
|
||||
$request_type = "single_account";
|
||||
if($request->input('branch_code') && !empty($request->input('stmt_sent_type'))){
|
||||
$request_type = 'multi_account'; // Default untuk request manual
|
||||
}
|
||||
|
||||
if($request_type=='single_account'){
|
||||
$account = Account::where('account_number', $accountNumber)->first();
|
||||
if ($account) {
|
||||
$branch_code = $account->branch_code;
|
||||
$userBranchId = session('branch_id'); // Assuming branch ID is stored in session
|
||||
$multiBranch = session('MULTI_BRANCH');
|
||||
|
||||
if (!$multiBranch) {
|
||||
// Check if account branch matches user's branch
|
||||
if ($account->branch_id !== $userBranchId) {
|
||||
return redirect()->route('statements.index')
|
||||
->with('error', 'Nomor rekening tidak sesuai dengan cabang Anda. Transaksi tidak dapat dilanjutkan.');
|
||||
if (!$multiBranch) {
|
||||
// Check if account branch matches user's branch
|
||||
if ($account->branch_id !== $userBranchId) {
|
||||
return redirect()->route('statements.index')
|
||||
->with('error', 'Nomor rekening tidak sesuai dengan cabang Anda. Transaksi tidak dapat dilanjutkan.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if account belongs to restricted branch ID0019999
|
||||
if ($account->branch_id === 'ID0019999') {
|
||||
// Check if account belongs to restricted branch ID0019999
|
||||
if ($account->branch_id === 'ID0019999') {
|
||||
return redirect()->route('statements.index')
|
||||
->with('error', 'Nomor rekening terdaftar pada cabang khusus. Silakan hubungi bagian HC untuk informasi lebih lanjut.');
|
||||
}
|
||||
|
||||
// If all checks pass, proceed with storing data
|
||||
// Your existing store logic here
|
||||
|
||||
} else {
|
||||
// Account not found
|
||||
return redirect()->route('statements.index')
|
||||
->with('error', 'Nomor rekening terdaftar pada cabang khusus. Silakan hubungi bagian HC untuk informasi lebih lanjut.');
|
||||
->with('error', 'Nomor rekening tidak ditemukan dalam sistem.');
|
||||
}
|
||||
|
||||
// If all checks pass, proceed with storing data
|
||||
// Your existing store logic here
|
||||
|
||||
} else {
|
||||
// Account not found
|
||||
return redirect()->route('statements.index')
|
||||
->with('error', 'Nomor rekening tidak ditemukan dalam sistem.');
|
||||
if($request->input('branch_code')=== 'ID0019999') {
|
||||
return redirect()->route('statements.index')
|
||||
->with('error', 'tidak dapat dilakukan print statement unruk cabang khusus. Silakan hubungi bagian HC untuk informasi lebih lanjut.');
|
||||
}
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
@@ -96,7 +108,7 @@ ini_set('max_execution_time', 300000);
|
||||
$validated['processed_accounts'] = 0;
|
||||
$validated['success_count'] = 0;
|
||||
$validated['failed_count'] = 0;
|
||||
$validated['stmt_sent_type'] = $request->input('stmt_sent_type') ? json_encode($request->input('stmt_sent_type')) : '';
|
||||
$validated['stmt_sent_type'] = $request->input('stmt_sent_type') ? implode(",",$request->input('stmt_sent_type')) : '';
|
||||
$validated['branch_code'] = $validated['branch_code'] ?? $branch_code; // Awal tidak tersedia
|
||||
|
||||
// Create the statement log
|
||||
@@ -116,10 +128,9 @@ ini_set('max_execution_time', 300000);
|
||||
$this->printStatementRekening($statement);
|
||||
}
|
||||
|
||||
$statement = PrintStatementLog::find($statement->id);
|
||||
if($statement->email){
|
||||
$this->sendEmail($statement->id);
|
||||
}
|
||||
//if($statement->email){
|
||||
// $this->sendEmail($statement->id);
|
||||
//}
|
||||
DB::commit();
|
||||
|
||||
return redirect()->route('statements.index')
|
||||
@@ -428,7 +439,10 @@ ini_set('max_execution_time', 300000);
|
||||
'file_path' => $filePath
|
||||
]);
|
||||
|
||||
return $disk->download($filePath, "{$statement->account_number}_{$statement->period_from}.pdf");
|
||||
return response()->download(
|
||||
$disk->path($filePath),
|
||||
"{$statement->account_number}_{$statement->period_from}.pdf"
|
||||
);
|
||||
} else {
|
||||
Log::warning('Statement file not found', [
|
||||
'statement_id' => $statement->id,
|
||||
@@ -489,7 +503,7 @@ ini_set('max_execution_time', 300000);
|
||||
$query = PrintStatementLog::query();
|
||||
$query->whereNotNull('user_id');
|
||||
|
||||
if (!auth()->user()->hasRole('administrator')) {
|
||||
if (!Auth::user()->role === 'administrator') {
|
||||
$query->where(function($q) {
|
||||
$q->where('user_id', Auth::id())
|
||||
->orWhere('branch_code', Auth::user()->branch->code);
|
||||
@@ -591,6 +605,7 @@ ini_set('max_execution_time', 300000);
|
||||
'authorized_by' => $item->authorizer ? $item->authorizer->name : null,
|
||||
'authorized_at' => $item->authorized_at ? $item->authorized_at->format('Y-m-d H:i:s') : null,
|
||||
'remarks' => $item->remarks,
|
||||
'request_type' => $item->request_type ?? 'N/A',
|
||||
];
|
||||
});
|
||||
|
||||
@@ -1274,6 +1289,7 @@ ini_set('max_execution_time', 300000);
|
||||
'branch_code' => $statement->branch_code
|
||||
]);
|
||||
|
||||
|
||||
if ($statement->request_type === 'multi_account') {
|
||||
return $this->processMultiAccountStatement($statement);
|
||||
} else {
|
||||
@@ -1307,7 +1323,6 @@ ini_set('max_execution_time', 300000);
|
||||
{
|
||||
try {
|
||||
$period = $statement->period_from ?? date('Ym');
|
||||
$clientName = 'client1';
|
||||
|
||||
// Validasi stmt_sent_type
|
||||
if (empty($statement->stmt_sent_type)) {
|
||||
@@ -1315,13 +1330,7 @@ ini_set('max_execution_time', 300000);
|
||||
}
|
||||
|
||||
// Decode stmt_sent_type jika dalam format JSON array
|
||||
$stmtSentTypes = is_string($statement->stmt_sent_type)
|
||||
? json_decode($statement->stmt_sent_type, true)
|
||||
: $statement->stmt_sent_type;
|
||||
|
||||
if (!is_array($stmtSentTypes)) {
|
||||
$stmtSentTypes = [$stmtSentTypes];
|
||||
}
|
||||
$stmtSentTypes = explode(',', $statement->stmt_sent_type);
|
||||
|
||||
Log::info('Processing multi account statement', [
|
||||
'statement_id' => $statement->id,
|
||||
@@ -1330,12 +1339,16 @@ ini_set('max_execution_time', 300000);
|
||||
'period' => $period
|
||||
]);
|
||||
|
||||
|
||||
|
||||
$clientName = $statement->branch_code.'_'.$period.'_';//.implode('_'.$stmtSentTypes);
|
||||
// Ambil accounts berdasarkan branch_code dan stmt_sent_type
|
||||
$accounts = Account::where('branch_code', $statement->branch_code)
|
||||
->whereIn('stmt_sent_type', $stmtSentTypes)
|
||||
->with('customer')
|
||||
->limit(5)
|
||||
->get();
|
||||
|
||||
|
||||
if ($accounts->isEmpty()) {
|
||||
throw new \Exception('No accounts found for the specified criteria');
|
||||
}
|
||||
@@ -1348,8 +1361,9 @@ ini_set('max_execution_time', 300000);
|
||||
|
||||
// Update statement log dengan informasi accounts
|
||||
$accountNumbers = $accounts->pluck('account_number')->toArray();
|
||||
|
||||
$statement->update([
|
||||
'target_accounts' => $accountNumbers,
|
||||
'target_accounts' => implode(",",$accountNumbers),
|
||||
'total_accounts' => $accounts->count(),
|
||||
'status' => 'processing',
|
||||
'started_at' => now()
|
||||
@@ -1522,7 +1536,7 @@ ini_set('max_execution_time', 300000);
|
||||
Log::info('Multi account ZIP downloaded', [
|
||||
'statement_id' => $statementId,
|
||||
'zip_file' => $zipFile,
|
||||
'user_id' => auth()->id()
|
||||
'user_id' => Auth::id()
|
||||
]);
|
||||
|
||||
return response()->download($zipPath, $filename, [
|
||||
|
||||
@@ -2,22 +2,31 @@
|
||||
|
||||
namespace Modules\Webstatement\Jobs;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use ZipArchive;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Support\Facades\{
|
||||
DB,
|
||||
Log,
|
||||
Storage
|
||||
};
|
||||
use Spatie\Browsershot\Browsershot;
|
||||
use Modules\Basicdata\Models\Branch;
|
||||
use Illuminate\Queue\{
|
||||
SerializesModels,
|
||||
InteractsWithQueue
|
||||
};
|
||||
use Modules\Webstatement\Models\{
|
||||
StmtEntry,
|
||||
AccountBalance,
|
||||
PrintStatementLog,
|
||||
ProcessedStatement,
|
||||
TempStmtNarrParam,
|
||||
TempStmtNarrFormat
|
||||
};
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
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 Modules\Webstatement\Models\PrintStatementLog;
|
||||
use Modules\Webstatement\Models\AccountBalance;
|
||||
use Modules\Webstatement\Models\StmtEntry;
|
||||
use Modules\Basicdata\Models\Branch;
|
||||
use Spatie\Browsershot\Browsershot;
|
||||
use ZipArchive;
|
||||
|
||||
class GenerateMultiAccountPdfJob implements ShouldQueue
|
||||
{
|
||||
@@ -28,6 +37,8 @@ class GenerateMultiAccountPdfJob implements ShouldQueue
|
||||
protected $period;
|
||||
protected $clientName;
|
||||
protected $chunkSize = 10; // Process 10 accounts at a time
|
||||
protected $startDate;
|
||||
protected $endDate;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
@@ -43,6 +54,36 @@ class GenerateMultiAccountPdfJob implements ShouldQueue
|
||||
$this->accounts = $accounts;
|
||||
$this->period = $period;
|
||||
$this->clientName = $clientName;
|
||||
|
||||
// Calculate period dates using same logic as ExportStatementPeriodJob
|
||||
$this->calculatePeriodDates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate start and end dates for the given period
|
||||
* Menggunakan logika yang sama dengan ExportStatementPeriodJob
|
||||
*/
|
||||
private function calculatePeriodDates(): void
|
||||
{
|
||||
$year = substr($this->period, 0, 4);
|
||||
$month = substr($this->period, 4, 2);
|
||||
|
||||
// Special case for May 2025 - start from 12th
|
||||
if ($this->period === '202505') {
|
||||
$this->startDate = Carbon::createFromDate($year, $month, 12)->startOfDay();
|
||||
} else {
|
||||
// For all other periods, start from 1st of the month
|
||||
$this->startDate = Carbon::createFromDate($year, $month, 1)->startOfDay();
|
||||
}
|
||||
|
||||
// End date is always the last day of the month
|
||||
$this->endDate = Carbon::createFromDate($year, $month, 1)->endOfMonth()->endOfDay();
|
||||
|
||||
Log::info('Period dates calculated for PDF generation', [
|
||||
'period' => $this->period,
|
||||
'start_date' => $this->startDate->format('Y-m-d'),
|
||||
'end_date' => $this->endDate->format('Y-m-d')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,12 +92,12 @@ class GenerateMultiAccountPdfJob implements ShouldQueue
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
Log::info('Starting multi account PDF generation', [
|
||||
'statement_id' => $this->statement->id,
|
||||
'total_accounts' => $this->accounts->count(),
|
||||
'period' => $this->period
|
||||
'period' => $this->period,
|
||||
'date_range' => $this->startDate->format('Y-m-d') . ' to ' . $this->endDate->format('Y-m-d')
|
||||
]);
|
||||
|
||||
$pdfFiles = [];
|
||||
@@ -77,6 +118,9 @@ class GenerateMultiAccountPdfJob implements ShouldQueue
|
||||
'pdf_path' => $pdfPath
|
||||
]);
|
||||
}
|
||||
|
||||
// Memory cleanup after each account
|
||||
gc_collect_cycles();
|
||||
} catch (Exception $e) {
|
||||
$failedCount++;
|
||||
$errors[] = [
|
||||
@@ -105,11 +149,11 @@ class GenerateMultiAccountPdfJob implements ShouldQueue
|
||||
'status' => $failedCount > 0 ? 'completed_with_errors' : 'completed',
|
||||
'completed_at' => now(),
|
||||
'is_available' => $zipPath ? true : false,
|
||||
'is_generated' => $zipPath ? true : false,
|
||||
'error_message' => !empty($errors) ? json_encode($errors) : null
|
||||
]);
|
||||
|
||||
DB::commit();
|
||||
|
||||
|
||||
Log::info('Multi account PDF generation completed', [
|
||||
'statement_id' => $this->statement->id,
|
||||
'success_count' => $successCount,
|
||||
@@ -118,7 +162,6 @@ class GenerateMultiAccountPdfJob implements ShouldQueue
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
DB::rollBack();
|
||||
|
||||
Log::error('Multi account PDF generation failed', [
|
||||
'statement_id' => $this->statement->id,
|
||||
@@ -139,6 +182,7 @@ class GenerateMultiAccountPdfJob implements ShouldQueue
|
||||
|
||||
/**
|
||||
* Generate PDF untuk satu account
|
||||
* Menggunakan data dari ProcessedStatement yang sudah diproses oleh ExportStatementPeriodJob
|
||||
*
|
||||
* @param Account $account
|
||||
* @return string|null Path to generated PDF
|
||||
@@ -146,10 +190,23 @@ class GenerateMultiAccountPdfJob implements ShouldQueue
|
||||
protected function generateAccountPdf($account)
|
||||
{
|
||||
try {
|
||||
// Get statement entries
|
||||
$stmtEntries = $this->getStatementEntries($account->account_number);
|
||||
// Prepare account query untuk processing
|
||||
$accountQuery = [
|
||||
'account_number' => $account->account_number,
|
||||
'period' => $this->period
|
||||
];
|
||||
|
||||
// Get saldo awal bulan
|
||||
// Get total entry count
|
||||
$totalCount = $this->getTotalEntryCount($account->account_number);
|
||||
|
||||
// Delete existing processed data dan process ulang
|
||||
$this->deleteExistingProcessedData($accountQuery);
|
||||
$this->processAndSaveStatementEntries($account, $totalCount);
|
||||
|
||||
// Get statement entries from ProcessedStatement (data yang sudah diproses)
|
||||
$stmtEntries = $this->getProcessedStatementEntries($account->account_number);
|
||||
|
||||
// Get saldo awal bulan menggunakan logika yang sama dengan ExportStatementPeriodJob
|
||||
$saldoAwalBulan = $this->getSaldoAwalBulan($account->account_number);
|
||||
|
||||
// Get branch info
|
||||
@@ -157,17 +214,23 @@ class GenerateMultiAccountPdfJob implements ShouldQueue
|
||||
|
||||
// Prepare images for PDF
|
||||
$images = $this->prepareImagesForPdf();
|
||||
|
||||
$headerImagePath = public_path('assets/media/images/bg-header-table.png');
|
||||
$headerTableBg = file_exists($headerImagePath)
|
||||
? base64_encode(file_get_contents($headerImagePath))
|
||||
: null;
|
||||
|
||||
// Render HTML
|
||||
$html = view('webstatement::statements.stmt', compact(
|
||||
'stmtEntries',
|
||||
'account',
|
||||
$html = view('webstatement::statements.stmt', [
|
||||
'stmtEntries' => $stmtEntries,
|
||||
'account' => $account,
|
||||
'customer' => $account->customer,
|
||||
'images',
|
||||
'branch',
|
||||
'images' => $images,
|
||||
'branch' => $branch,
|
||||
'period' => $this->period,
|
||||
'saldoAwalBulan'
|
||||
))->render();
|
||||
'saldoAwalBulan' => $saldoAwalBulan,
|
||||
'headerTableBg' => $headerTableBg,
|
||||
])->render();
|
||||
|
||||
// Generate PDF filename
|
||||
$filename = "statement_{$account->account_number}_{$this->period}_" . now()->format('YmdHis') . '.pdf';
|
||||
@@ -182,11 +245,12 @@ class GenerateMultiAccountPdfJob implements ShouldQueue
|
||||
|
||||
// Generate PDF using Browsershot
|
||||
Browsershot::html($html)
|
||||
->showBackground()
|
||||
->setOption('addStyleTag', json_encode(['content' => '@page { margin: 0; }']))
|
||||
->format('A4')
|
||||
->margins(0, 0, 0, 0)
|
||||
->waitUntilNetworkIdle()
|
||||
->timeout(60)
|
||||
->timeout(60000)
|
||||
->save($pdfPath);
|
||||
|
||||
// Verify file was created
|
||||
@@ -194,6 +258,9 @@ class GenerateMultiAccountPdfJob implements ShouldQueue
|
||||
throw new Exception('PDF file was not created');
|
||||
}
|
||||
|
||||
// Clear variables to free memory
|
||||
unset($html, $stmtEntries, $images);
|
||||
|
||||
return $pdfPath;
|
||||
|
||||
} catch (Exception $e) {
|
||||
@@ -207,43 +274,342 @@ class GenerateMultiAccountPdfJob implements ShouldQueue
|
||||
}
|
||||
|
||||
/**
|
||||
* Get statement entries untuk account
|
||||
* Get total entry count untuk account
|
||||
* Menggunakan logika yang sama dengan ExportStatementPeriodJob
|
||||
*
|
||||
* @param string $accountNumber
|
||||
* @return int
|
||||
*/
|
||||
protected function getTotalEntryCount($accountNumber): int
|
||||
{
|
||||
$query = StmtEntry::where('account_number', $accountNumber)
|
||||
->whereBetween('booking_date', [
|
||||
$this->startDate->format('Ymd'),
|
||||
$this->endDate->format('Ymd')
|
||||
]);
|
||||
|
||||
Log::info("Getting total entry count for PDF generation", [
|
||||
'account' => $accountNumber,
|
||||
'start_date' => $this->startDate->format('Ymd'),
|
||||
'end_date' => $this->endDate->format('Ymd')
|
||||
]);
|
||||
|
||||
return $query->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete existing processed data untuk account
|
||||
* Menggunakan logika yang sama dengan ExportStatementPeriodJob
|
||||
*
|
||||
* @param array $criteria
|
||||
* @return void
|
||||
*/
|
||||
protected function deleteExistingProcessedData(array $criteria): void
|
||||
{
|
||||
Log::info('Deleting existing processed data for PDF generation', [
|
||||
'account_number' => $criteria['account_number'],
|
||||
'period' => $criteria['period']
|
||||
]);
|
||||
|
||||
ProcessedStatement::where('account_number', $criteria['account_number'])
|
||||
->where('period', $criteria['period'])
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process dan save statement entries untuk account
|
||||
* Menggunakan logika yang sama dengan ExportStatementPeriodJob
|
||||
*
|
||||
* @param Account $account
|
||||
* @param int $totalCount
|
||||
* @return void
|
||||
*/
|
||||
protected function processAndSaveStatementEntries($account, int $totalCount): void
|
||||
{
|
||||
// Get saldo awal dari AccountBalance
|
||||
$saldoAwalBulan = $this->getSaldoAwalBulan($account->account_number);
|
||||
$runningBalance = (float) $saldoAwalBulan->actual_balance;
|
||||
$globalSequence = 0;
|
||||
|
||||
Log::info("Processing {$totalCount} statement entries for PDF generation", [
|
||||
'account_number' => $account->account_number,
|
||||
'starting_balance' => $runningBalance
|
||||
]);
|
||||
|
||||
StmtEntry::with(['ft', 'transaction'])
|
||||
->where('account_number', $account->account_number)
|
||||
->whereBetween('booking_date', [
|
||||
$this->startDate->format('Ymd'),
|
||||
$this->endDate->format('Ymd')
|
||||
])
|
||||
->orderBy('date_time', 'ASC')
|
||||
->orderBy('trans_reference', 'ASC')
|
||||
->chunk(1000, function ($entries) use (&$runningBalance, &$globalSequence, $account) {
|
||||
$processedData = $this->prepareProcessedData($entries, $runningBalance, $globalSequence, $account->account_number);
|
||||
|
||||
if (!empty($processedData)) {
|
||||
DB::table('processed_statements')->insert($processedData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare processed data untuk batch insert
|
||||
* Menggunakan logika yang sama dengan ExportStatementPeriodJob
|
||||
*
|
||||
* @param $entries
|
||||
* @param float $runningBalance
|
||||
* @param int $globalSequence
|
||||
* @param string $accountNumber
|
||||
* @return array
|
||||
*/
|
||||
protected function prepareProcessedData($entries, &$runningBalance, &$globalSequence, $accountNumber): array
|
||||
{
|
||||
$processedData = [];
|
||||
|
||||
foreach ($entries as $item) {
|
||||
$globalSequence++;
|
||||
$runningBalance += (float) $item->amount_lcy;
|
||||
|
||||
$actualDate = $this->formatActualDate($item);
|
||||
|
||||
$processedData[] = [
|
||||
'account_number' => $accountNumber,
|
||||
'period' => $this->period,
|
||||
'sequence_no' => $globalSequence,
|
||||
'transaction_date' => $item->booking_date,
|
||||
'reference_number' => $item->trans_reference,
|
||||
'transaction_amount' => $item->amount_lcy,
|
||||
'transaction_type' => $item->amount_lcy < 0 ? 'D' : 'C',
|
||||
'description' => $this->generateNarrative($item),
|
||||
'end_balance' => $runningBalance,
|
||||
'actual_date' => $actualDate,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
];
|
||||
}
|
||||
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format actual date dari item
|
||||
* Menggunakan logika yang sama dengan ExportStatementPeriodJob
|
||||
*
|
||||
* @param $item
|
||||
* @return string
|
||||
*/
|
||||
protected function formatActualDate($item): string
|
||||
{
|
||||
try {
|
||||
$prefix = substr($item->trans_reference ?? '', 0, 2);
|
||||
$relationMap = [
|
||||
'FT' => 'ft',
|
||||
'TT' => 'tt',
|
||||
'DC' => 'dc',
|
||||
'AA' => 'aa'
|
||||
];
|
||||
|
||||
$datetime = $item->date_time;
|
||||
if (isset($relationMap[$prefix])) {
|
||||
$relation = $relationMap[$prefix];
|
||||
$datetime = $item->$relation?->date_time ?? $datetime;
|
||||
}
|
||||
|
||||
return Carbon::createFromFormat(
|
||||
'ymdHi',
|
||||
$datetime
|
||||
)->format('d/m/Y H:i');
|
||||
} catch (Exception $e) {
|
||||
Log::warning("Error formatting actual date: " . $e->getMessage());
|
||||
return Carbon::now()->format('d/m/Y H:i');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate narrative untuk statement entry
|
||||
* Menggunakan logika yang sama dengan ExportStatementPeriodJob
|
||||
*
|
||||
* @param $item
|
||||
* @return string
|
||||
*/
|
||||
protected function generateNarrative($item)
|
||||
{
|
||||
$narr = [];
|
||||
|
||||
if ($item->transaction) {
|
||||
if ($item->transaction->stmt_narr) {
|
||||
$narr[] = $item->transaction->stmt_narr;
|
||||
}
|
||||
if ($item->narrative) {
|
||||
$narr[] = $item->narrative;
|
||||
}
|
||||
if ($item->transaction->narr_type) {
|
||||
$narr[] = $this->getFormatNarrative($item->transaction->narr_type, $item);
|
||||
}
|
||||
} else if ($item->narrative) {
|
||||
$narr[] = $item->narrative;
|
||||
}
|
||||
|
||||
if ($item->ft?->recipt_no) {
|
||||
$narr[] = 'Receipt No: ' . $item->ft->recipt_no;
|
||||
}
|
||||
|
||||
return implode(' ', array_filter($narr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted narrative berdasarkan narrative type
|
||||
* Menggunakan logika yang sama dengan ExportStatementPeriodJob
|
||||
*
|
||||
* @param $narr
|
||||
* @param $item
|
||||
* @return string
|
||||
*/
|
||||
protected function getFormatNarrative($narr, $item)
|
||||
{
|
||||
|
||||
$narrParam = TempStmtNarrParam::where('_id', $narr)->first();
|
||||
|
||||
if (!$narrParam) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$fmt = '';
|
||||
if ($narrParam->_id == 'FTIN') {
|
||||
$fmt = 'FT.IN';
|
||||
} else if ($narrParam->_id == 'FTOUT') {
|
||||
$fmt = 'FT.OUT';
|
||||
} else if ($narrParam->_id == 'TTTRFOUT') {
|
||||
$fmt = 'TT.O.TRF';
|
||||
} else if ($narrParam->_id == 'TTTRFIN') {
|
||||
$fmt = 'TT.I.TRF';
|
||||
} else if ($narrParam->_id == 'APITRX'){
|
||||
$fmt = 'API.TSEL';
|
||||
} else if ($narrParam->_id == 'ONUSCR'){
|
||||
$fmt = 'ONUS.CR';
|
||||
} else if ($narrParam->_id == 'ONUSDR'){
|
||||
$fmt = 'ONUS.DR';
|
||||
}else {
|
||||
$fmt = $narrParam->_id;
|
||||
}
|
||||
|
||||
$narrFormat = TempStmtNarrFormat::where('_id', $fmt)->first();
|
||||
|
||||
if (!$narrFormat) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Get the format string from the database
|
||||
$formatString = $narrFormat->text_data ?? '';
|
||||
|
||||
// Parse the format string
|
||||
// Split by the separator ']'
|
||||
$parts = explode(']', $formatString);
|
||||
|
||||
$result = '';
|
||||
|
||||
foreach ($parts as $index => $part) {
|
||||
if (empty($part)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($index === 0) {
|
||||
// For the first part, take only what's before the '!'
|
||||
$splitPart = explode('!', $part);
|
||||
if (count($splitPart) > 0) {
|
||||
// Remove quotes, backslashes, and other escape characters
|
||||
$cleanPart = trim($splitPart[0]).' ';
|
||||
// Remove quotes at the beginning and end
|
||||
$cleanPart = preg_replace('/^["\'\\\\]+|["\'\\\\]+$/', '', $cleanPart);
|
||||
// Remove any remaining backslashes
|
||||
$cleanPart = str_replace('\\', '', $cleanPart);
|
||||
// Remove any remaining quotes
|
||||
$cleanPart = str_replace('"', '', $cleanPart);
|
||||
$result .= $cleanPart;
|
||||
}
|
||||
} else {
|
||||
// For other parts, these are field placeholders
|
||||
$fieldName = strtolower(str_replace('.', '_', $part));
|
||||
|
||||
// Get the corresponding parameter value from narrParam
|
||||
$paramValue = null;
|
||||
|
||||
// Check if the field exists as a property in narrParam
|
||||
if (property_exists($narrParam, $fieldName)) {
|
||||
$paramValue = $narrParam->$fieldName;
|
||||
} else if (isset($narrParam->$fieldName)) {
|
||||
$paramValue = $narrParam->$fieldName;
|
||||
}
|
||||
|
||||
// If we found a value, add it to the result
|
||||
if ($paramValue !== null) {
|
||||
$result .= $paramValue;
|
||||
} else {
|
||||
// If no value found, try to use the original field name as a fallback
|
||||
if ($fieldName !== 'recipt_no') {
|
||||
$prefix = substr($item->trans_reference ?? '', 0, 2);
|
||||
$relationMap = [
|
||||
'FT' => 'ft',
|
||||
'TT' => 'tt',
|
||||
'DC' => 'dc',
|
||||
'AA' => 'aa'
|
||||
];
|
||||
|
||||
if (isset($relationMap[$prefix])) {
|
||||
$relation = $relationMap[$prefix];
|
||||
$result .= ($item->$relation?->$fieldName ?? '') . ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return str_replace('<NL>', ' ', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get processed statement entries untuk account
|
||||
* Menggunakan data dari tabel ProcessedStatement yang sudah diproses
|
||||
*
|
||||
* @param string $accountNumber
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
protected function getStatementEntries($accountNumber)
|
||||
protected function getProcessedStatementEntries($accountNumber)
|
||||
{
|
||||
// Calculate period dates
|
||||
$year = substr($this->period, 0, 4);
|
||||
$month = substr($this->period, 4, 2);
|
||||
Log::info('Getting processed statement entries', [
|
||||
'account_number' => $accountNumber,
|
||||
'period' => $this->period
|
||||
]);
|
||||
|
||||
if ($this->period === '202505') {
|
||||
$startDate = Carbon::createFromDate($year, $month, 12)->startOfDay();
|
||||
} else {
|
||||
$startDate = Carbon::createFromDate($year, $month, 1)->startOfDay();
|
||||
}
|
||||
|
||||
$endDate = Carbon::createFromDate($year, $month, 1)->endOfMonth()->endOfDay();
|
||||
|
||||
return StmtEntry::where('account_number', $accountNumber)
|
||||
->whereBetween('booking_date', [
|
||||
$startDate->format('Ymd'),
|
||||
$endDate->format('Ymd')
|
||||
])
|
||||
->orderBy('date_time', 'ASC')
|
||||
->orderBy('trans_reference', 'ASC')
|
||||
return ProcessedStatement::where('account_number', $accountNumber)
|
||||
->where('period', $this->period)
|
||||
->orderBy('sequence_no', 'ASC')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get saldo awal bulan untuk account
|
||||
* Menggunakan logika yang sama dengan ExportStatementPeriodJob
|
||||
*
|
||||
* @param string $accountNumber
|
||||
* @return object
|
||||
*/
|
||||
protected function getSaldoAwalBulan($accountNumber)
|
||||
{
|
||||
// Menggunakan logika yang sama dengan ExportStatementPeriodJob
|
||||
// Ambil saldo dari ProcessedStatement entry pertama dikurangi transaction_amount
|
||||
$firstEntry = ProcessedStatement::where('account_number', $accountNumber)
|
||||
->where('period', $this->period)
|
||||
->orderBy('sequence_no', 'ASC')
|
||||
->first();
|
||||
|
||||
if ($firstEntry) {
|
||||
$saldoAwal = $firstEntry->end_balance - $firstEntry->transaction_amount;
|
||||
return (object) ['actual_balance' => $saldoAwal];
|
||||
}
|
||||
|
||||
// Fallback ke AccountBalance jika tidak ada ProcessedStatement
|
||||
$saldoPeriod = $this->calculateSaldoPeriod($this->period);
|
||||
|
||||
$saldo = AccountBalance::where('account_number', $accountNumber)
|
||||
@@ -255,6 +621,7 @@ class GenerateMultiAccountPdfJob implements ShouldQueue
|
||||
|
||||
/**
|
||||
* Calculate saldo period berdasarkan aturan bisnis
|
||||
* Menggunakan logika yang sama dengan ExportStatementPeriodJob
|
||||
*
|
||||
* @param string $period
|
||||
* @return string
|
||||
@@ -299,6 +666,7 @@ class GenerateMultiAccountPdfJob implements ShouldQueue
|
||||
$images[$key] = base64_encode(file_get_contents($fullPath));
|
||||
} else {
|
||||
$images[$key] = null;
|
||||
Log::warning('Image file not found', ['path' => $fullPath]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,6 +716,13 @@ class GenerateMultiAccountPdfJob implements ShouldQueue
|
||||
'statement_id' => $this->statement->id
|
||||
]);
|
||||
|
||||
// Clean up individual PDF files after creating ZIP
|
||||
foreach ($pdfFiles as $pdfFile) {
|
||||
if (file_exists($pdfFile)) {
|
||||
unlink($pdfFile);
|
||||
}
|
||||
}
|
||||
|
||||
return $zipPath;
|
||||
|
||||
} catch (Exception $e) {
|
||||
|
||||
@@ -350,6 +350,12 @@
|
||||
},
|
||||
account_number: {
|
||||
title: 'Account Number',
|
||||
render: (item, data) => {
|
||||
if(data.request_type=="multi_account"){
|
||||
return data.stmt_sent_type ?? 'N/A';
|
||||
}
|
||||
return data.account_number ?? '';
|
||||
},
|
||||
},
|
||||
period: {
|
||||
title: 'Period',
|
||||
|
||||
Reference in New Issue
Block a user