Compare commits
7 Commits
5de1c19d09
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5752427297 | ||
|
|
eb89916b1c | ||
|
|
80c866f646 | ||
|
|
e5c33bf631 | ||
|
|
f37707b2f6 | ||
|
|
ad9780ccd6 | ||
|
|
bcc6d814e9 |
@@ -64,16 +64,6 @@ class GenerateClosingBalanceReportBulkCommand extends Command
|
||||
'IDR1354400010001',
|
||||
'IDR1728500010001',
|
||||
'IDR1728600010001',
|
||||
'IDR1354500010001',
|
||||
'IDR1354500020001',
|
||||
'IDR1354500030001',
|
||||
'IDR1354500040001',
|
||||
'IDR1354500050001',
|
||||
'IDR1354500060001',
|
||||
'IDR1354500070001',
|
||||
'IDR1354500080001',
|
||||
'IDR1354500090001',
|
||||
'IDR1354500100001',
|
||||
'IDR1720500010001',
|
||||
'1078333878',
|
||||
'1081647484',
|
||||
@@ -89,6 +79,19 @@ class GenerateClosingBalanceReportBulkCommand extends Command
|
||||
'IDR1354200010001'
|
||||
];
|
||||
|
||||
private $qrisAccount = [
|
||||
'IDR1354500010001',
|
||||
'IDR1354500020001',
|
||||
'IDR1354500030001',
|
||||
'IDR1354500040001',
|
||||
'IDR1354500050001',
|
||||
'IDR1354500060001',
|
||||
'IDR1354500070001',
|
||||
'IDR1354500080001',
|
||||
'IDR1354500090001',
|
||||
'IDR1354500100001',
|
||||
];
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
* Menjalankan proses generate laporan closing balance untuk banyak rekening dengan periode range
|
||||
@@ -281,7 +284,7 @@ class GenerateClosingBalanceReportBulkCommand extends Command
|
||||
}
|
||||
|
||||
// Jika tidak ada parameter accounts, gunakan default list
|
||||
$accountList = ['DEFAULT' => $this->defaultAccounts];
|
||||
$accountList = ['DEFAULT' => $this->defaultAccounts, 'QRIS' => $this->qrisAccount];
|
||||
|
||||
// Filter by client jika ada (untuk backward compatibility)
|
||||
if ($clientFilter) {
|
||||
@@ -378,7 +381,7 @@ class GenerateClosingBalanceReportBulkCommand extends Command
|
||||
}
|
||||
|
||||
// Dispatch the job
|
||||
GenerateClosingBalanceReportJob::dispatch($accountNumber, $period, $reportLog->id);
|
||||
GenerateClosingBalanceReportJob::dispatch($accountNumber, $period, $reportLog->id, $groupName);
|
||||
|
||||
DB::commit();
|
||||
|
||||
|
||||
@@ -1,51 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Webstatement\Console;
|
||||
namespace Modules\Webstatement\Console;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Modules\Webstatement\Http\Controllers\MigrasiController;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Modules\Webstatement\Http\Controllers\MigrasiController;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ProcessDailyMigration extends Command
|
||||
class ProcessDailyMigration extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'webstatement:process-daily-migration
|
||||
{--process_parameter= : To process migration parameter true/false}
|
||||
{--period= : Period to process (default: -1 day, format: Ymd or relative date)}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Process data migration for the specified period (default: previous day)';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'webstatement:process-daily-migration
|
||||
{--process_parameter= : To process migration parameter true/false}';
|
||||
$processParameter = $this->option('process_parameter');
|
||||
$period = $this->option('period');
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Process data migration for the previous day\'s period';
|
||||
// Log start of process
|
||||
Log::info('Starting daily data migration process', [
|
||||
'process_parameter' => $processParameter ?? 'false',
|
||||
'period' => $period ?? '-1 day'
|
||||
]);
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$processParameter = $this->option('process_parameter');
|
||||
$this->info('Starting daily data migration process...');
|
||||
$this->info('Process Parameter: ' . ($processParameter ?? 'False'));
|
||||
$this->info('Period: ' . ($period ?? '-1 day (default)'));
|
||||
|
||||
$this->info('Starting daily data migration process...');
|
||||
$this->info('Process Parameter: ' . ($processParameter ?? 'False'));
|
||||
try {
|
||||
$controller = app(MigrasiController::class);
|
||||
$response = $controller->index($processParameter, $period);
|
||||
|
||||
try {
|
||||
$controller = app(MigrasiController::class);
|
||||
$response = $controller->index($processParameter);
|
||||
$responseData = json_decode($response->getContent(), true);
|
||||
$message = $responseData['message'] ?? 'Process completed';
|
||||
|
||||
$responseData = json_decode($response->getContent(), true);
|
||||
$this->info($responseData['message'] ?? 'Process completed');
|
||||
$this->info($message);
|
||||
Log::info('Daily migration process completed successfully', ['message' => $message]);
|
||||
|
||||
return Command::SUCCESS;
|
||||
} catch (Exception $e) {
|
||||
$this->error('Error processing daily migration: ' . $e->getMessage());
|
||||
return Command::FAILURE;
|
||||
}
|
||||
return Command::SUCCESS;
|
||||
} catch (Exception $e) {
|
||||
$errorMessage = 'Error processing daily migration: ' . $e->getMessage();
|
||||
$this->error($errorMessage);
|
||||
Log::error($errorMessage, ['exception' => $e->getTraceAsString()]);
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,72 +136,6 @@ class LaporanClosingBalanceController extends Controller
|
||||
return view('webstatement::laporan-closing-balance.show', compact('closingBalanceReport'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Download laporan jika tersedia
|
||||
*
|
||||
* @param ClosingBalanceReportLog $closingBalanceReport
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function download(ClosingBalanceReportLog $closingBalanceReport)
|
||||
{
|
||||
Log::info('Download laporan closing balance', [
|
||||
'report_id' => $closingBalanceReport->id,
|
||||
'user_id' => Auth::id()
|
||||
]);
|
||||
|
||||
try {
|
||||
// Check if report is available
|
||||
if ($closingBalanceReport->status !== 'completed' || !$closingBalanceReport->file_path) {
|
||||
Log::warning('Laporan tidak tersedia untuk download', [
|
||||
'report_id' => $closingBalanceReport->id,
|
||||
'status' => $closingBalanceReport->status
|
||||
]);
|
||||
return back()->with('error', 'Laporan tidak tersedia untuk download.');
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
// Update download status
|
||||
$closingBalanceReport->update([
|
||||
'is_downloaded' => true,
|
||||
'downloaded_at' => now(),
|
||||
'updated_by' => Auth::id()
|
||||
]);
|
||||
|
||||
DB::commit();
|
||||
|
||||
// Download the file
|
||||
$filePath = $closingBalanceReport->file_path;
|
||||
if (Storage::exists($filePath)) {
|
||||
$fileName = "closing_balance_report_{$closingBalanceReport->account_number}_{$closingBalanceReport->period}.csv";
|
||||
|
||||
Log::info('File laporan berhasil didownload', [
|
||||
'report_id' => $closingBalanceReport->id,
|
||||
'file_path' => $filePath
|
||||
]);
|
||||
|
||||
return Storage::download($filePath, $fileName);
|
||||
}
|
||||
|
||||
Log::error('File laporan tidak ditemukan', [
|
||||
'report_id' => $closingBalanceReport->id,
|
||||
'file_path' => $filePath
|
||||
]);
|
||||
|
||||
return back()->with('error', 'File laporan tidak ditemukan.');
|
||||
|
||||
} catch (Exception $e) {
|
||||
DB::rollback();
|
||||
|
||||
Log::error('Error saat download laporan', [
|
||||
'report_id' => $closingBalanceReport->id,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
|
||||
return back()->with('error', 'Terjadi kesalahan saat download laporan.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorize permintaan laporan
|
||||
*
|
||||
@@ -273,15 +207,53 @@ class LaporanClosingBalanceController extends Controller
|
||||
// Retrieve data from the database
|
||||
$query = ClosingBalanceReportLog::query();
|
||||
|
||||
// Apply search filter if provided
|
||||
// Apply search filter if provided (handle JSON search parameters)
|
||||
if ($request->has('search') && !empty($request->get('search'))) {
|
||||
$search = $request->get('search');
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('account_number', 'LIKE', "%$search%")
|
||||
->orWhere('period', 'LIKE', "%$search%")
|
||||
->orWhere('status', 'LIKE', "%$search%")
|
||||
->orWhere('authorization_status', 'LIKE', "%$search%");
|
||||
});
|
||||
|
||||
// Check if search is JSON format
|
||||
if (is_string($search) && json_decode($search, true) !== null) {
|
||||
$searchParams = json_decode($search, true);
|
||||
|
||||
// Apply account number filter
|
||||
if (!empty($searchParams['account_number'])) {
|
||||
$query->where('account_number', 'LIKE', "%{$searchParams['account_number']}%");
|
||||
}
|
||||
|
||||
// Apply date range filter
|
||||
if (!empty($searchParams['start_date'])) {
|
||||
$startPeriod = Carbon::createFromFormat('Y-m-d', $searchParams['start_date'])->format('Ymd');
|
||||
$query->where('period', '>=', $startPeriod);
|
||||
}
|
||||
|
||||
if (!empty($searchParams['end_date'])) {
|
||||
$endPeriod = Carbon::createFromFormat('Y-m-d', $searchParams['end_date'])->format('Ymd');
|
||||
$query->where('period', '<=', $endPeriod);
|
||||
}
|
||||
} else {
|
||||
// Handle regular string search (fallback)
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('account_number', 'LIKE', "%$search%")
|
||||
->orWhere('period', 'LIKE', "%$search%")
|
||||
->orWhere('status', 'LIKE', "%$search%")
|
||||
->orWhere('authorization_status', 'LIKE', "%$search%");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Apply individual parameter filters (for backward compatibility)
|
||||
if ($request->has('account_number') && !empty($request->get('account_number'))) {
|
||||
$query->where('account_number', 'LIKE', "%{$request->get('account_number')}%");
|
||||
}
|
||||
|
||||
if ($request->has('start_date') && !empty($request->get('start_date'))) {
|
||||
$startPeriod = Carbon::createFromFormat('Y-m-d', $request->get('start_date'))->format('Ymd');
|
||||
$query->where('period', '>=', $startPeriod);
|
||||
}
|
||||
|
||||
if ($request->has('end_date') && !empty($request->get('end_date'))) {
|
||||
$endPeriod = Carbon::createFromFormat('Y-m-d', $request->get('end_date'))->format('Ymd');
|
||||
$query->where('period', '<=', $endPeriod);
|
||||
}
|
||||
|
||||
// Apply column filters if provided
|
||||
@@ -519,4 +491,81 @@ class LaporanClosingBalanceController extends Controller
|
||||
return back()->with('error', 'Gagal mengulang generate laporan: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download laporan berdasarkan nomor rekening dan periode
|
||||
*
|
||||
* @param string $accountNumber
|
||||
* @param string $period
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function download($accountNumber, $period)
|
||||
{
|
||||
Log::info('Download laporan closing balance', [
|
||||
'account_number' => $accountNumber,
|
||||
'period' => $period,
|
||||
'user_id' => Auth::id()
|
||||
]);
|
||||
|
||||
try {
|
||||
// Cari laporan berdasarkan account number dan period
|
||||
$closingBalanceReport = ClosingBalanceReportLog::where('account_number', $accountNumber)
|
||||
->where('period', $period)
|
||||
->where('status', 'completed')
|
||||
->whereNotNull('file_path')
|
||||
->first();
|
||||
|
||||
if (!$closingBalanceReport) {
|
||||
Log::warning('Laporan tidak ditemukan atau belum selesai', [
|
||||
'account_number' => $accountNumber,
|
||||
'period' => $period
|
||||
]);
|
||||
return back()->with('error', 'Laporan tidak ditemukan atau belum selesai diproses.');
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
// Update download status
|
||||
$closingBalanceReport->update([
|
||||
'is_downloaded' => true,
|
||||
'downloaded_at' => now(),
|
||||
'updated_by' => Auth::id()
|
||||
]);
|
||||
|
||||
DB::commit();
|
||||
|
||||
// Download the file
|
||||
$filePath = $closingBalanceReport->file_path;
|
||||
if (Storage::exists($filePath)) {
|
||||
$fileName = "closing_balance_report_{$accountNumber}_{$period}.csv";
|
||||
|
||||
Log::info('File laporan berhasil didownload', [
|
||||
'account_number' => $accountNumber,
|
||||
'period' => $period,
|
||||
'file_path' => $filePath
|
||||
]);
|
||||
|
||||
return Storage::download($filePath, $fileName);
|
||||
}
|
||||
|
||||
Log::error('File laporan tidak ditemukan di storage', [
|
||||
'account_number' => $accountNumber,
|
||||
'period' => $period,
|
||||
'file_path' => $filePath
|
||||
]);
|
||||
|
||||
return back()->with('error', 'File laporan tidak ditemukan.');
|
||||
|
||||
} catch (Exception $e) {
|
||||
DB::rollback();
|
||||
|
||||
Log::error('Error saat download laporan', [
|
||||
'account_number' => $accountNumber,
|
||||
'period' => $period,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
|
||||
return back()->with('error', 'Terjadi kesalahan saat mengunduh laporan: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
ProcessTellerDataJob,
|
||||
ProcessTransactionDataJob,
|
||||
ProcessSectorDataJob,
|
||||
ProcessProvinceDataJob};
|
||||
ProcessProvinceDataJob,
|
||||
ProcessStmtEntryDetailDataJob};
|
||||
|
||||
class MigrasiController extends Controller
|
||||
{
|
||||
@@ -38,6 +39,7 @@
|
||||
'customer' => ProcessCustomerDataJob::class,
|
||||
'account' => ProcessAccountDataJob::class,
|
||||
'stmtEntry' => ProcessStmtEntryDataJob::class,
|
||||
'stmtEntryDetail' => ProcessStmtEntryDetailDataJob::class, // Tambahan baru
|
||||
'dataCapture' => ProcessDataCaptureDataJob::class,
|
||||
'fundsTransfer' => ProcessFundsTransferDataJob::class,
|
||||
'teller' => ProcessTellerDataJob::class,
|
||||
@@ -63,6 +65,7 @@
|
||||
'customer',
|
||||
'account',
|
||||
'stmtEntry',
|
||||
'stmtEntryDetail', // Tambahan baru
|
||||
'dataCapture',
|
||||
'fundsTransfer',
|
||||
'teller',
|
||||
@@ -98,30 +101,99 @@
|
||||
return response()->json(['error' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
public function index($processParameter = false)
|
||||
/**
|
||||
* Proses migrasi data dengan parameter dan periode yang dapat dikustomisasi
|
||||
*
|
||||
* @param bool|string $processParameter Flag untuk memproses parameter
|
||||
* @param string|null $period Periode yang akan diproses (default: -1 day)
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function index($processParameter = false, $period = null)
|
||||
{
|
||||
$disk = Storage::disk('sftpStatement');
|
||||
try {
|
||||
Log::info('Starting migration process', [
|
||||
'process_parameter' => $processParameter,
|
||||
'period' => $period
|
||||
]);
|
||||
|
||||
if ($processParameter) {
|
||||
foreach (self::PARAMETER_PROCESSES as $process) {
|
||||
$this->processData($process, '_parameter');
|
||||
$disk = Storage::disk('sftpStatement');
|
||||
|
||||
if ($processParameter) {
|
||||
Log::info('Processing parameter data');
|
||||
|
||||
foreach (self::PARAMETER_PROCESSES as $process) {
|
||||
$this->processData($process, '_parameter');
|
||||
}
|
||||
|
||||
Log::info('Parameter processes completed successfully');
|
||||
return response()->json(['message' => 'Parameter processes completed successfully']);
|
||||
}
|
||||
return response()->json(['message' => 'Parameter processes completed successfully']);
|
||||
}
|
||||
|
||||
$period = date('Ymd', strtotime('-1 day'));
|
||||
if (!$disk->exists($period)) {
|
||||
// Tentukan periode yang akan diproses
|
||||
$targetPeriod = $this->determinePeriod($period);
|
||||
|
||||
Log::info('Processing data for period', ['period' => $targetPeriod]);
|
||||
|
||||
if (!$disk->exists($targetPeriod)) {
|
||||
$errorMessage = "Period {$targetPeriod} folder not found in SFTP storage";
|
||||
Log::warning($errorMessage);
|
||||
|
||||
return response()->json([
|
||||
"message" => $errorMessage
|
||||
], 404);
|
||||
}
|
||||
|
||||
foreach (self::DATA_PROCESSES as $process) {
|
||||
$this->processData($process, $targetPeriod);
|
||||
}
|
||||
|
||||
$successMessage = "Data processing for period {$targetPeriod} has been queued successfully";
|
||||
Log::info($successMessage);
|
||||
|
||||
return response()->json([
|
||||
"message" => "Period {$period} folder not found in SFTP storage"
|
||||
], 404);
|
||||
'message' => $successMessage
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
Log::error('Error in migration index method: ' . $e->getMessage());
|
||||
return response()->json(['error' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tentukan periode berdasarkan input atau gunakan default
|
||||
*
|
||||
* @param string|null $period Input periode
|
||||
* @return string Periode dalam format Ymd
|
||||
*/
|
||||
private function determinePeriod($period = null): string
|
||||
{
|
||||
if ($period === null) {
|
||||
// Default: -1 day
|
||||
$calculatedPeriod = date('Ymd', strtotime('-1 day'));
|
||||
Log::info('Using default period', ['period' => $calculatedPeriod]);
|
||||
return $calculatedPeriod;
|
||||
}
|
||||
|
||||
foreach (self::DATA_PROCESSES as $process) {
|
||||
$this->processData($process, $period);
|
||||
// Jika periode sudah dalam format Ymd (8 digit)
|
||||
if (preg_match('/^\d{8}$/', $period)) {
|
||||
Log::info('Using provided period in Ymd format', ['period' => $period]);
|
||||
return $period;
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => "Data processing for period {$period} has been queued successfully"
|
||||
]);
|
||||
// Jika periode dalam format relative date (contoh: -2 days, -1 week, etc.)
|
||||
try {
|
||||
$calculatedPeriod = date('Ymd', strtotime($period));
|
||||
Log::info('Calculated period from relative date', [
|
||||
'input' => $period,
|
||||
'calculated' => $calculatedPeriod
|
||||
]);
|
||||
return $calculatedPeriod;
|
||||
} catch (Exception $e) {
|
||||
Log::warning('Invalid period format, using default', [
|
||||
'input' => $period,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
return date('Ymd', strtotime('-1 day'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +114,12 @@
|
||||
],
|
||||
'SWADAYA_PANDU' => [
|
||||
'0081272689',
|
||||
],
|
||||
"AWAN_LINTANG_SOLUSI"=> [
|
||||
"1084269430"
|
||||
],
|
||||
"MONETA"=> [
|
||||
"1085667890"
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
@@ -191,11 +191,11 @@
|
||||
$subQuery->where('product_code', '!=', '6021')
|
||||
->orWhere(function($nestedQuery) {
|
||||
$nestedQuery->where('product_code', '6021')
|
||||
->where('ctdesc', '!=', 'gold');
|
||||
->where('ctdesc', '!=', 'GOLD');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
$cards = $query->get();
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
namespace Modules\Webstatement\Jobs;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
@@ -12,9 +10,14 @@ use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Modules\Webstatement\Models\AccountBalance;
|
||||
use Modules\Webstatement\Models\ClosingBalanceReportLog;
|
||||
use Modules\Webstatement\Models\StmtEntry;
|
||||
use Modules\Webstatement\Models\StmtEntryDetail;
|
||||
use Modules\Webstatement\Models\TempFundsTransfer;
|
||||
use Modules\Webstatement\Models\DataCapture;
|
||||
|
||||
/**
|
||||
* Job untuk generate laporan closing balance
|
||||
@@ -27,6 +30,7 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
|
||||
protected $accountNumber;
|
||||
protected $period;
|
||||
protected $reportLogId;
|
||||
protected $groupName;
|
||||
protected $chunkSize = 1000;
|
||||
protected $disk = 'local';
|
||||
|
||||
@@ -37,11 +41,12 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
|
||||
* @param string $period
|
||||
* @param int $reportLogId
|
||||
*/
|
||||
public function __construct(string $accountNumber, string $period, int $reportLogId)
|
||||
public function __construct(string $accountNumber, string $period, int $reportLogId, string $groupName='DEFAULT')
|
||||
{
|
||||
$this->accountNumber = $accountNumber;
|
||||
$this->period = $period;
|
||||
$this->reportLogId = $reportLogId;
|
||||
$this->groupName = $groupName ?? 'DEFAULT';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,106 +163,316 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate report data based on the provided SQL query
|
||||
* Menggenerate data laporan berdasarkan query yang diberikan
|
||||
* Build transaction query using pure Eloquent relationships
|
||||
* Membangun query transaksi menggunakan relasi Eloquent murni
|
||||
*/
|
||||
private function generateReportData(float $openingBalance): array
|
||||
private function buildTransactionQuery()
|
||||
{
|
||||
Log::info('Generating closing balance report data', [
|
||||
Log::info('Building transaction query using pure Eloquent relationships', [
|
||||
'group_name' => $this->groupName,
|
||||
'account_number' => $this->accountNumber,
|
||||
'period' => $this->period
|
||||
]);
|
||||
|
||||
// Tentukan model berdasarkan group name
|
||||
$modelClass = $this->getModelByGroup();
|
||||
|
||||
// Build query menggunakan pure Eloquent dengan eager loading
|
||||
$query = $modelClass::with([
|
||||
'ft' => function($query) {
|
||||
$query->select([
|
||||
'_id',
|
||||
'ref_no',
|
||||
'debit_acct_no',
|
||||
'debit_value_date',
|
||||
'credit_acct_no',
|
||||
'bif_rcv_acct',
|
||||
'bif_rcv_name',
|
||||
'credit_value_date',
|
||||
'at_unique_id',
|
||||
'bif_ref_no',
|
||||
'atm_order_id',
|
||||
'recipt_no',
|
||||
'api_iss_acct',
|
||||
'api_benff_acct',
|
||||
'authoriser',
|
||||
'remarks',
|
||||
'payment_details',
|
||||
'merchant_id',
|
||||
'term_id',
|
||||
'date_time'
|
||||
]);
|
||||
},
|
||||
'dc' => function($query) {
|
||||
$query->select([
|
||||
'id',
|
||||
'date_time'
|
||||
]);
|
||||
}
|
||||
])
|
||||
->select([
|
||||
'id',
|
||||
'trans_reference',
|
||||
'booking_date',
|
||||
'amount_lcy',
|
||||
'date_time'
|
||||
])
|
||||
->where('account_number', $this->accountNumber)
|
||||
->where('booking_date', $this->period)
|
||||
->orderBy('booking_date')
|
||||
->orderBy('date_time');
|
||||
|
||||
Log::info('Transaction query built successfully using pure Eloquent', [
|
||||
'model_class' => $modelClass,
|
||||
'account_number' => $this->accountNumber,
|
||||
'period' => $this->period
|
||||
]);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get model class based on group name
|
||||
* Mendapatkan class model berdasarkan group name
|
||||
*/
|
||||
private function getModelByGroup()
|
||||
{
|
||||
Log::info('Determining model by group', [
|
||||
'group_name' => $this->groupName
|
||||
]);
|
||||
|
||||
$model = $this->groupName === 'QRIS' ? StmtEntryDetail::class : StmtEntry::class;
|
||||
|
||||
Log::info('Model determined', [
|
||||
'group_name' => $this->groupName,
|
||||
'model_class' => $model
|
||||
]);
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process transaction data from ORM result
|
||||
* Memproses data transaksi dari hasil ORM
|
||||
*/
|
||||
private function processTransactionData($transaction): array
|
||||
{
|
||||
Log::info('Processing transaction data', [
|
||||
'trans_reference' => $transaction->trans_reference,
|
||||
'has_ft_relation' => !is_null($transaction->ft),
|
||||
'has_dc_relation' => !is_null($transaction->dc)
|
||||
]);
|
||||
|
||||
// Hitung debit dan credit amount
|
||||
$debitAmount = $transaction->amount_lcy < 0 ? abs($transaction->amount_lcy) : null;
|
||||
$creditAmount = $transaction->amount_lcy > 0 ? $transaction->amount_lcy : null;
|
||||
|
||||
// Ambil date_time dari prioritas: ft -> dc -> stmt
|
||||
$dateTime = $transaction->ft?->date_time ??
|
||||
$transaction->dc?->date_time ??
|
||||
$transaction->date_time;
|
||||
|
||||
$processedData = [
|
||||
'trans_reference' => $transaction->trans_reference,
|
||||
'booking_date' => $transaction->booking_date,
|
||||
'amount_lcy' => $transaction->amount_lcy,
|
||||
'debit_amount' => $debitAmount,
|
||||
'credit_amount' => $creditAmount,
|
||||
'date_time' => $dateTime,
|
||||
// Data dari TempFundsTransfer melalui relasi
|
||||
'debit_acct_no' => $transaction->ft?->debit_acct_no,
|
||||
'debit_value_date' => $transaction->ft?->debit_value_date,
|
||||
'credit_acct_no' => $transaction->ft?->credit_acct_no,
|
||||
'bif_rcv_acct' => $transaction->ft?->bif_rcv_acct,
|
||||
'bif_rcv_name' => $transaction->ft?->bif_rcv_name,
|
||||
'credit_value_date' => $transaction->ft?->credit_value_date,
|
||||
'at_unique_id' => $transaction->ft?->at_unique_id,
|
||||
'bif_ref_no' => $transaction->ft?->bif_ref_no,
|
||||
'atm_order_id' => $transaction->ft?->atm_order_id,
|
||||
'recipt_no' => $transaction->ft?->recipt_no,
|
||||
'api_iss_acct' => $transaction->ft?->api_iss_acct,
|
||||
'api_benff_acct' => $transaction->ft?->api_benff_acct,
|
||||
'authoriser' => $transaction->ft?->authoriser,
|
||||
'remarks' => $transaction->ft?->remarks,
|
||||
'payment_details' => $transaction->ft?->payment_details,
|
||||
'ref_no' => $transaction->ft?->ref_no,
|
||||
'merchant_id' => $transaction->ft?->merchant_id,
|
||||
'term_id' => $transaction->ft?->term_id,
|
||||
];
|
||||
|
||||
Log::info('Transaction data processed successfully', [
|
||||
'trans_reference' => $transaction->trans_reference,
|
||||
'final_date_time' => $dateTime,
|
||||
'debit_amount' => $debitAmount,
|
||||
'credit_amount' => $creditAmount
|
||||
]);
|
||||
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
'opening_balance' => $openingBalance
|
||||
'group_name' => $this->groupName,
|
||||
'chunk_size' => $this->chunkSize
|
||||
]);
|
||||
|
||||
$reportData = [];
|
||||
$runningBalance = $openingBalance;
|
||||
$sequenceNo = 0;
|
||||
$runningBalance = $this->getOpeningBalance();
|
||||
$sequenceNo = 1;
|
||||
|
||||
// Query berdasarkan SQL yang diberikan user
|
||||
$query = DB::table('stmt_entry as s')
|
||||
->leftJoin('temp_funds_transfer as ft', 'ft._id', '=', 's.trans_reference')
|
||||
->leftJoin('data_captures as dc', 'dc.id', '=', 's.trans_reference')
|
||||
->select([
|
||||
's.trans_reference',
|
||||
's.booking_date',
|
||||
's.amount_lcy',
|
||||
'ft.debit_acct_no',
|
||||
'ft.debit_value_date',
|
||||
DB::raw('CASE WHEN s.amount_lcy::numeric < 0 THEN s.amount_lcy::numeric ELSE NULL END AS debit_amount'),
|
||||
'ft.credit_acct_no',
|
||||
'ft.bif_rcv_acct',
|
||||
'ft.bif_rcv_name',
|
||||
'ft.credit_value_date',
|
||||
DB::raw('CASE WHEN s.amount_lcy::numeric > 0 THEN s.amount_lcy::numeric ELSE NULL END AS credit_amount'),
|
||||
'ft.at_unique_id',
|
||||
'ft.bif_ref_no',
|
||||
'ft.atm_order_id',
|
||||
'ft.recipt_no',
|
||||
'ft.api_iss_acct',
|
||||
'ft.api_benff_acct',
|
||||
DB::raw('COALESCE(ft.date_time, dc.date_time, s.date_time) AS date_time'),
|
||||
'ft.authoriser',
|
||||
'ft.remarks',
|
||||
'ft.payment_details',
|
||||
'ft.ref_no',
|
||||
'ft.merchant_id',
|
||||
'ft.term_id'
|
||||
])
|
||||
->where('s.account_number', $this->accountNumber)
|
||||
->where('s.booking_date', $this->period)
|
||||
->orderBy('s.booking_date')
|
||||
->orderBy('date_time');
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
// Process data in chunks
|
||||
$query->chunk($this->chunkSize, function ($transactions) use (&$reportData, &$runningBalance, &$sequenceNo) {
|
||||
foreach ($transactions as $transaction) {
|
||||
$sequenceNo++;
|
||||
// Build query menggunakan pure ORM
|
||||
$query = $this->buildTransactionQuery();
|
||||
|
||||
// Calculate running balance
|
||||
$runningBalance += (float) $transaction->amount_lcy;
|
||||
// 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
|
||||
]);
|
||||
|
||||
// Format transaction date
|
||||
$transactionDate = $this->formatDateTime($transaction->date_time);
|
||||
foreach ($transactions as $transaction) {
|
||||
// Process transaction data
|
||||
$processedData = $this->processTransactionData($transaction);
|
||||
|
||||
$reportData[] = [
|
||||
'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
|
||||
];
|
||||
}
|
||||
});
|
||||
// Update running balance
|
||||
$amount = (float) $transaction->amount_lcy;
|
||||
$runningBalance += $amount;
|
||||
|
||||
Log::info('Report data generated', [
|
||||
'total_records' => count($reportData),
|
||||
'final_balance' => $runningBalance
|
||||
]);
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table name based on group name
|
||||
* Mendapatkan nama tabel berdasarkan group name
|
||||
*/
|
||||
private function getTableNameByGroup(): string
|
||||
{
|
||||
return $this->groupName === 'QRIS' ? 'stmt_entry' : 'stmt_entry_details';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get select fields for the query
|
||||
* Mendapatkan field select untuk query
|
||||
*/
|
||||
private function getSelectFields(): array
|
||||
{
|
||||
return [
|
||||
's.trans_reference',
|
||||
's.booking_date',
|
||||
's.amount_lcy',
|
||||
'ft.debit_acct_no',
|
||||
'ft.debit_value_date',
|
||||
DB::raw('CASE WHEN s.amount_lcy::numeric < 0 THEN s.amount_lcy::numeric ELSE NULL END AS debit_amount'),
|
||||
'ft.credit_acct_no',
|
||||
'ft.bif_rcv_acct',
|
||||
'ft.bif_rcv_name',
|
||||
'ft.credit_value_date',
|
||||
DB::raw('CASE WHEN s.amount_lcy::numeric > 0 THEN s.amount_lcy::numeric ELSE NULL END AS credit_amount'),
|
||||
'ft.at_unique_id',
|
||||
'ft.bif_ref_no',
|
||||
'ft.atm_order_id',
|
||||
'ft.recipt_no',
|
||||
'ft.api_iss_acct',
|
||||
'ft.api_benff_acct',
|
||||
DB::raw('COALESCE(ft.date_time, dc.date_time, s.date_time) AS date_time'),
|
||||
'ft.authoriser',
|
||||
'ft.remarks',
|
||||
'ft.payment_details',
|
||||
'ft.ref_no',
|
||||
'ft.merchant_id',
|
||||
'ft.term_id'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format datetime string
|
||||
* Memformat string datetime
|
||||
|
||||
399
app/Jobs/ProcessStmtEntryDetailDataJob.php
Normal file
399
app/Jobs/ProcessStmtEntryDetailDataJob.php
Normal file
@@ -0,0 +1,399 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Webstatement\Jobs;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Modules\Webstatement\Models\StmtEntryDetail;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ProcessStmtEntryDetailDataJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
private const CSV_DELIMITER = '~';
|
||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||
private const FILENAME = 'ST.STMT.ENTRY.DETAIL.csv';
|
||||
private const DISK_NAME = 'sftpStatement';
|
||||
private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage
|
||||
|
||||
private string $period = '';
|
||||
private int $processedCount = 0;
|
||||
private int $errorCount = 0;
|
||||
private array $entryBatch = [];
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param string $period Periode data yang akan diproses
|
||||
*/
|
||||
public function __construct(string $period = '')
|
||||
{
|
||||
$this->period = $period;
|
||||
Log::info('ProcessStmtEntryDetailDataJob initialized', ['period' => $period]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
Log::info('Memulai ProcessStmtEntryDetailDataJob', ['period' => $this->period]);
|
||||
|
||||
$this->initializeJob();
|
||||
|
||||
if ($this->period === '') {
|
||||
Log::warning('No period provided for statement entry detail data processing');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->processPeriod();
|
||||
$this->logJobCompletion();
|
||||
|
||||
Log::info('ProcessStmtEntryDetailDataJob selesai berhasil');
|
||||
} catch (Exception $e) {
|
||||
Log::error('Error in ProcessStmtEntryDetailDataJob: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inisialisasi job dengan pengaturan awal
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function initializeJob(): void
|
||||
{
|
||||
set_time_limit(self::MAX_EXECUTION_TIME);
|
||||
$this->processedCount = 0;
|
||||
$this->errorCount = 0;
|
||||
$this->entryBatch = [];
|
||||
|
||||
Log::info('Job initialized', [
|
||||
'max_execution_time' => self::MAX_EXECUTION_TIME,
|
||||
'chunk_size' => self::CHUNK_SIZE
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proses data untuk periode tertentu
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function processPeriod(): void
|
||||
{
|
||||
$disk = Storage::disk(self::DISK_NAME);
|
||||
$filename = "{$this->period}." . self::FILENAME;
|
||||
$filePath = "{$this->period}/$filename";
|
||||
|
||||
Log::info('Memulai proses periode', ['file_path' => $filePath]);
|
||||
|
||||
if (!$this->validateFile($disk, $filePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tempFilePath = $this->createTemporaryFile($disk, $filePath, $filename);
|
||||
$this->processFile($tempFilePath, $filePath);
|
||||
$this->cleanup($tempFilePath);
|
||||
|
||||
Log::info('Proses periode selesai', ['file_path' => $filePath]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validasi keberadaan file
|
||||
*
|
||||
* @param mixed $disk Storage disk instance
|
||||
* @param string $filePath Path file yang akan divalidasi
|
||||
* @return bool
|
||||
*/
|
||||
private function validateFile($disk, string $filePath): bool
|
||||
{
|
||||
Log::info("Processing statement entry detail file: $filePath");
|
||||
|
||||
if (!$disk->exists($filePath)) {
|
||||
Log::warning("File not found: $filePath");
|
||||
return false;
|
||||
}
|
||||
|
||||
Log::info("File validated successfully: $filePath");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Buat file temporary untuk proses
|
||||
*
|
||||
* @param mixed $disk Storage disk instance
|
||||
* @param string $filePath Path file sumber
|
||||
* @param string $filename Nama file
|
||||
* @return string Path file temporary
|
||||
*/
|
||||
private function createTemporaryFile($disk, string $filePath, string $filename): string
|
||||
{
|
||||
$tempFilePath = storage_path("app/temp_$filename");
|
||||
file_put_contents($tempFilePath, $disk->get($filePath));
|
||||
|
||||
Log::info('Temporary file created', ['temp_path' => $tempFilePath]);
|
||||
return $tempFilePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proses file CSV
|
||||
*
|
||||
* @param string $tempFilePath Path file temporary
|
||||
* @param string $filePath Path file asli
|
||||
* @return void
|
||||
*/
|
||||
private function processFile(string $tempFilePath, string $filePath): void
|
||||
{
|
||||
$handle = fopen($tempFilePath, "r");
|
||||
if ($handle === false) {
|
||||
Log::error("Unable to open file: $filePath");
|
||||
return;
|
||||
}
|
||||
|
||||
$headers = (new StmtEntryDetail())->getFillable();
|
||||
// Tambahkan field 'id' ke headers untuk menangani kolom tambahan di akhir CSV
|
||||
$expectedHeaders = array_merge($headers, ['id']);
|
||||
$rowCount = 0;
|
||||
$chunkCount = 0;
|
||||
|
||||
Log::info('Memulai proses file', [
|
||||
'file_path' => $filePath,
|
||||
'headers_count' => count($headers),
|
||||
'expected_headers_count' => count($expectedHeaders)
|
||||
]);
|
||||
|
||||
while (($row = fgetcsv($handle, 0, self::CSV_DELIMITER)) !== false) {
|
||||
$rowCount++;
|
||||
$this->processRow($row, $expectedHeaders, $rowCount, $filePath);
|
||||
|
||||
// Process in chunks to avoid memory issues
|
||||
if (count($this->entryBatch) >= self::CHUNK_SIZE) {
|
||||
$this->saveBatch();
|
||||
$chunkCount++;
|
||||
Log::info("Processed chunk $chunkCount ({$this->processedCount} records so far)");
|
||||
}
|
||||
}
|
||||
|
||||
// Process any remaining records
|
||||
if (!empty($this->entryBatch)) {
|
||||
$this->saveBatch();
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
Log::info("Completed processing $filePath. Processed {$this->processedCount} records with {$this->errorCount} errors.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Proses setiap baris data dengan penanganan field id tambahan
|
||||
*
|
||||
* @param array $row Data baris
|
||||
* @param array $expectedHeaders Header kolom yang diharapkan (termasuk id)
|
||||
* @param int $rowCount Nomor baris
|
||||
* @param string $filePath Path file
|
||||
* @return void
|
||||
*/
|
||||
private function processRow(array $row, array $expectedHeaders, int $rowCount, string $filePath): void
|
||||
{
|
||||
// Validasi jumlah kolom - sekarang menggunakan expectedHeaders yang sudah include field 'id'
|
||||
if (count($expectedHeaders) !== count($row)) {
|
||||
Log::warning("Row $rowCount in $filePath has incorrect column count. Expected: " .
|
||||
count($expectedHeaders) . ", Got: " . count($row));
|
||||
$this->errorCount++;
|
||||
return;
|
||||
}
|
||||
|
||||
// Kombinasikan data dengan headers
|
||||
$data = array_combine($expectedHeaders, $row);
|
||||
|
||||
// Log untuk debugging struktur data
|
||||
Log::debug('Processing row data', [
|
||||
'row_count' => $rowCount,
|
||||
'stmt_entry_id' => $data['stmt_entry_id'] ?? 'not_set',
|
||||
'id' => $data['id'] ?? 'not_set'
|
||||
]);
|
||||
|
||||
// Logika untuk menggunakan field 'id' sebagai fallback jika stmt_entry_id kosong
|
||||
$this->handleStmtEntryIdFallback($data);
|
||||
|
||||
// Hapus field 'id' dari data sebelum disimpan karena tidak ada di fillable model
|
||||
unset($data['id']);
|
||||
|
||||
$this->cleanTransReference($data);
|
||||
$this->addToBatch($data, $rowCount, $filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Menangani logika fallback untuk stmt_entry_id menggunakan field id
|
||||
*
|
||||
* @param array $data Data yang akan diproses
|
||||
* @return void
|
||||
*/
|
||||
private function handleStmtEntryIdFallback(array &$data): void
|
||||
{
|
||||
// Jika stmt_entry_id kosong atau null, gunakan value dari field 'id'
|
||||
if (empty($data['stmt_entry_id']) || $data['stmt_entry_id'] === '' || $data['stmt_entry_id'] === null) {
|
||||
if (isset($data['id']) && !empty($data['id'])) {
|
||||
$data['stmt_entry_id'] = $data['id'];
|
||||
|
||||
Log::info('Using id as stmt_entry_id fallback', [
|
||||
'original_stmt_entry_id' => $data['stmt_entry_id'] ?? 'empty',
|
||||
'fallback_id' => $data['id']
|
||||
]);
|
||||
} else {
|
||||
Log::warning('Both stmt_entry_id and id are empty', [
|
||||
'stmt_entry_id' => $data['stmt_entry_id'] ?? 'not_set',
|
||||
'id' => $data['id'] ?? 'not_set'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tambahkan record ke batch untuk proses bulk insert
|
||||
*
|
||||
* @param array $data Data record
|
||||
* @param int $rowCount Nomor baris
|
||||
* @param string $filePath Path file
|
||||
* @return void
|
||||
*/
|
||||
private function addToBatch(array $data, int $rowCount, string $filePath): void
|
||||
{
|
||||
try {
|
||||
// Validasi bahwa stmt_entry_id tidak kosong dan bukan header
|
||||
if (isset($data['stmt_entry_id']) &&
|
||||
$data['stmt_entry_id'] !== 'stmt_entry_id' &&
|
||||
!empty($data['stmt_entry_id'])) {
|
||||
|
||||
// Add timestamp fields
|
||||
$now = now();
|
||||
$data['created_at'] = $now;
|
||||
$data['updated_at'] = $now;
|
||||
|
||||
// Add to entry batch
|
||||
$this->entryBatch[] = $data;
|
||||
$this->processedCount++;
|
||||
|
||||
Log::debug('Record added to batch', [
|
||||
'row' => $rowCount,
|
||||
'stmt_entry_id' => $data['stmt_entry_id']
|
||||
]);
|
||||
} else {
|
||||
Log::warning('Skipping row due to invalid stmt_entry_id', [
|
||||
'row' => $rowCount,
|
||||
'stmt_entry_id' => $data['stmt_entry_id'] ?? 'not_set'
|
||||
]);
|
||||
$this->errorCount++;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->errorCount++;
|
||||
Log::error("Error processing Statement Entry Detail at row $rowCount in $filePath: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bersihkan trans_reference dari karakter tidak diinginkan
|
||||
*
|
||||
* @param array $data Data yang akan dibersihkan
|
||||
* @return void
|
||||
*/
|
||||
private function cleanTransReference(array &$data): void
|
||||
{
|
||||
if (isset($data['trans_reference'])) {
|
||||
// Clean trans_reference from \\BNK if present
|
||||
$data['trans_reference'] = preg_replace('/\\\\.*$/', '', $data['trans_reference']);
|
||||
Log::debug('Trans reference cleaned', ['original' => $data['trans_reference']]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simpan batch data ke database menggunakan updateOrCreate
|
||||
* untuk menghindari error unique constraint
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function saveBatch(): void
|
||||
{
|
||||
Log::info('Memulai proses saveBatch dengan updateOrCreate');
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
if (!empty($this->entryBatch)) {
|
||||
$totalProcessed = 0;
|
||||
|
||||
// Process each entry data directly
|
||||
foreach ($this->entryBatch as $entryData) {
|
||||
// Validasi bahwa entryData adalah array dan memiliki stmt_entry_id
|
||||
if (is_array($entryData) && isset($entryData['stmt_entry_id'])) {
|
||||
// Gunakan updateOrCreate untuk menghindari duplicate key error
|
||||
StmtEntryDetail::updateOrCreate(
|
||||
[
|
||||
'stmt_entry_id' => $entryData['stmt_entry_id']
|
||||
],
|
||||
$entryData
|
||||
);
|
||||
|
||||
$totalProcessed++;
|
||||
} else {
|
||||
Log::warning('Invalid entry data structure', ['data' => $entryData]);
|
||||
$this->errorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
Log::info("Berhasil memproses {$totalProcessed} record dengan updateOrCreate");
|
||||
|
||||
// Reset entry batch after successful processing
|
||||
$this->entryBatch = [];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
DB::rollback();
|
||||
|
||||
Log::error("Error in saveBatch: " . $e->getMessage() . "\n" . $e->getTraceAsString());
|
||||
$this->errorCount += count($this->entryBatch);
|
||||
|
||||
// Reset batch even if there's an error to prevent reprocessing the same failed records
|
||||
$this->entryBatch = [];
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bersihkan file temporary
|
||||
*
|
||||
* @param string $tempFilePath Path file temporary
|
||||
* @return void
|
||||
*/
|
||||
private function cleanup(string $tempFilePath): void
|
||||
{
|
||||
if (file_exists($tempFilePath)) {
|
||||
unlink($tempFilePath);
|
||||
Log::info('Temporary file cleaned up', ['temp_path' => $tempFilePath]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log penyelesaian job
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function logJobCompletion(): void
|
||||
{
|
||||
Log::info("Statement Entry Detail data processing completed. " .
|
||||
"Total processed: {$this->processedCount}, Total errors: {$this->errorCount}");
|
||||
}
|
||||
}
|
||||
113
app/Models/StmtEntryDetail.php
Normal file
113
app/Models/StmtEntryDetail.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Webstatement\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
class StmtEntryDetail extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'stmt_entry_detail';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'stmt_entry_id',
|
||||
'account_number',
|
||||
'company_code',
|
||||
'amount_lcy',
|
||||
'transaction_code',
|
||||
'narrative',
|
||||
'product_category',
|
||||
'value_date',
|
||||
'amount_fcy',
|
||||
'exchange_rate',
|
||||
'trans_reference',
|
||||
'booking_date',
|
||||
'stmt_no',
|
||||
'date_time',
|
||||
'currency',
|
||||
'crf_type',
|
||||
'consol_key',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* Relasi ke model Account
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo(Account::class, 'account_number', 'account_number');
|
||||
}
|
||||
|
||||
/**
|
||||
* Relasi ke model TempFundsTransfer
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function ft()
|
||||
{
|
||||
return $this->belongsTo(TempFundsTransfer::class, 'trans_reference', 'ref_no');
|
||||
}
|
||||
|
||||
/**
|
||||
* Relasi ke model TempTransaction
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function transaction()
|
||||
{
|
||||
return $this->belongsTo(TempTransaction::class, 'transaction_code', 'transaction_code');
|
||||
}
|
||||
|
||||
/**
|
||||
* Relasi ke model Teller
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function tt()
|
||||
{
|
||||
return $this->belongsTo(Teller::class, 'trans_reference', 'id_teller');
|
||||
}
|
||||
|
||||
/**
|
||||
* Relasi ke model DataCapture
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function dc()
|
||||
{
|
||||
return $this->belongsTo(DataCapture::class, 'trans_reference', 'id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Relasi ke model TempArrangement
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function aa()
|
||||
{
|
||||
return $this->belongsTo(TempArrangement::class, 'trans_reference', 'arrangement_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateStmtEntryDetailTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('stmt_entry_detail', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('stmt_entry_id')->nullable();
|
||||
$table->string('account_number')->nullable();
|
||||
$table->string('company_code')->nullable();
|
||||
$table->string('amount_lcy')->nullable();
|
||||
$table->string('transaction_code')->nullable();
|
||||
$table->string('narrative')->nullable();
|
||||
$table->string('product_category')->nullable();
|
||||
$table->string('value_date')->nullable();
|
||||
$table->string('amount_fcy')->nullable();
|
||||
$table->string('exchange_rate')->nullable();
|
||||
$table->string('trans_reference')->nullable();
|
||||
$table->string('booking_date')->nullable();
|
||||
$table->string('stmt_no')->nullable();
|
||||
$table->string('date_time')->nullable();
|
||||
$table->string('currency')->nullable();
|
||||
$table->string('crf_type')->nullable();
|
||||
$table->string('consol_key')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
// Index untuk performa query
|
||||
$table->index('stmt_entry_id');
|
||||
$table->index('account_number');
|
||||
$table->index('trans_reference');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('stmt_entry_detail');
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,10 @@
|
||||
|
||||
@section('content')
|
||||
<div class="grid">
|
||||
<div class="card card-grid min-w-full" data-datatable="false" data-datatable-page-size="10" data-datatable-state-save="false" id="laporan-closing-balance-table" data-api-url="{{ route('laporan-closing-balance.datatables') }}">
|
||||
<div class="card-header py-5 flex-wrap">
|
||||
<div class="min-w-full card card-grid" data-datatable="false" data-datatable-page-size="10"
|
||||
data-datatable-state-save="false" id="laporan-closing-balance-table"
|
||||
data-api-url="{{ route('laporan-closing-balance.datatables') }}">
|
||||
<div class="flex-wrap py-5 card-header">
|
||||
<h3 class="card-title">
|
||||
Laporan Closing Balance
|
||||
</h3>
|
||||
@@ -16,93 +18,73 @@
|
||||
<div class="flex flex-wrap gap-2.5 items-end">
|
||||
<!-- Nomor Rekening Filter -->
|
||||
<div class="flex flex-col">
|
||||
<label class="text-sm font-medium text-gray-700 mb-1">Nomor Rekening</label>
|
||||
<input type="text" id="account-number-filter" class="input w-[200px]" placeholder="Masukkan nomor rekening">
|
||||
<input type="text" id="account-number-filter" class="input w-[200px]"
|
||||
placeholder="Masukkan nomor rekening">
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Tanggal Mulai Filter -->
|
||||
<div class="flex flex-col">
|
||||
<label class="text-sm font-medium text-gray-700 mb-1">Tanggal Mulai</label>
|
||||
<input type="date" id="start-date-filter" class="input w-[150px]">
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Tanggal Akhir Filter -->
|
||||
<div class="flex flex-col">
|
||||
<label class="text-sm font-medium text-gray-700 mb-1">Tanggal Akhir</label>
|
||||
<input type="date" id="end-date-filter" class="input w-[150px]">
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Tombol Filter -->
|
||||
<button type="button" id="apply-filter" class="btn btn-primary">
|
||||
<i class="ki-filled ki-magnifier"></i>
|
||||
Filter
|
||||
</button>
|
||||
|
||||
|
||||
<!-- Tombol Reset -->
|
||||
<button type="button" id="reset-filter" class="btn btn-light">
|
||||
<i class="ki-filled ki-arrows-circle"></i>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2.5">
|
||||
<div class="h-[24px] border border-r-gray-200"></div>
|
||||
<a class="btn btn-light" href="{{ route('laporan-closing-balance.export') }}" id="export-btn">
|
||||
<i class="ki-filled ki-file-down"></i>
|
||||
Export to CSV
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="scrollable-x-auto">
|
||||
<table class="table table-auto table-border align-middle text-gray-700 font-medium text-sm" data-datatable-table="true">
|
||||
<table class="table text-sm font-medium text-gray-700 align-middle table-auto table-border"
|
||||
data-datatable-table="true">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-14">
|
||||
<input class="checkbox checkbox-sm" data-datatable-check="true" type="checkbox"/>
|
||||
</th>
|
||||
<th class="min-w-[200px]" data-datatable-column="account_number">
|
||||
<span class="sort">
|
||||
<span class="sort-label">Nomor Rekening</span>
|
||||
<span class="sort-icon"></span>
|
||||
</span>
|
||||
</th>
|
||||
<th class="min-w-[150px]" data-datatable-column="period">
|
||||
<span class="sort">
|
||||
<span class="sort-label">Periode</span>
|
||||
<span class="sort-icon"></span>
|
||||
</span>
|
||||
</th>
|
||||
<th class="min-w-[150px]" data-datatable-column="cleared_balance">
|
||||
<span class="sort">
|
||||
<span class="sort-label">Saldo Cleared</span>
|
||||
<span class="sort-icon"></span>
|
||||
</span>
|
||||
</th>
|
||||
<th class="min-w-[150px]" data-datatable-column="actual_balance">
|
||||
<span class="sort">
|
||||
<span class="sort-label">Saldo Akhir</span>
|
||||
<span class="sort-icon"></span>
|
||||
</span>
|
||||
</th>
|
||||
<th class="min-w-[150px]" data-datatable-column="updated_at">
|
||||
<span class="sort">
|
||||
<span class="sort-label">Tanggal Update</span>
|
||||
<span class="sort-icon"></span>
|
||||
</span>
|
||||
</th>
|
||||
<th class="min-w-[100px] text-center" data-datatable-column="actions">Action</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="w-14">
|
||||
<input class="checkbox checkbox-sm" data-datatable-check="true" type="checkbox" />
|
||||
</th>
|
||||
<th class="min-w-[200px]" data-datatable-column="account_number">
|
||||
<span class="sort">
|
||||
<span class="sort-label">Nomor Rekening</span>
|
||||
<span class="sort-icon"></span>
|
||||
</span>
|
||||
</th>
|
||||
<th class="min-w-[150px]" data-datatable-column="period">
|
||||
<span class="sort">
|
||||
<span class="sort-label">Periode</span>
|
||||
<span class="sort-icon"></span>
|
||||
</span>
|
||||
</th>
|
||||
<th class="min-w-[150px]" data-datatable-column="updated_at">
|
||||
<span class="sort">
|
||||
<span class="sort-label">Tanggal Update</span>
|
||||
<span class="sort-icon"></span>
|
||||
</span>
|
||||
</th>
|
||||
<th class="min-w-[100px] text-center" data-datatable-column="actions">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card-footer justify-center md:justify-between flex-col md:flex-row gap-3 text-gray-600 text-2sm font-medium">
|
||||
<div class="flex items-center gap-2">
|
||||
<div
|
||||
class="flex-col gap-3 justify-center font-medium text-gray-600 card-footer md:justify-between md:flex-row text-2sm">
|
||||
<div class="flex gap-2 items-center">
|
||||
Show
|
||||
<select class="select select-sm w-16" data-datatable-size="true" name="perpage"></select> per page
|
||||
<select class="w-16 select select-sm" data-datatable-size="true" name="perpage"></select> per page
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex gap-4 items-center">
|
||||
<span data-datatable-info="true"></span>
|
||||
<div class="pagination" data-datatable-pagination="true">
|
||||
</div>
|
||||
@@ -135,11 +117,11 @@
|
||||
*/
|
||||
function formatPeriod(period) {
|
||||
if (!period || period.length !== 8) return period;
|
||||
|
||||
|
||||
const year = period.substring(0, 4);
|
||||
const month = period.substring(4, 6);
|
||||
const day = period.substring(6, 8);
|
||||
|
||||
|
||||
return `${day}/${month}/${year}`;
|
||||
}
|
||||
|
||||
@@ -150,7 +132,7 @@
|
||||
*/
|
||||
function formatDate(dateString) {
|
||||
if (!dateString) return '-';
|
||||
|
||||
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('id-ID', {
|
||||
year: 'numeric',
|
||||
@@ -171,11 +153,25 @@
|
||||
const exportBtn = document.getElementById('export-btn');
|
||||
|
||||
const apiUrl = element.getAttribute('data-api-url');
|
||||
|
||||
// Konfigurasi DataTable
|
||||
|
||||
// Set default date range (last 30 days) SEBELUM inisialisasi DataTable
|
||||
const today = new Date();
|
||||
const thirtyDaysAgo = new Date(today.getTime() - (30 * 24 * 60 * 60 * 1000));
|
||||
|
||||
endDateFilter.value = today.toISOString().split('T')[0];
|
||||
startDateFilter.value = thirtyDaysAgo.toISOString().split('T')[0];
|
||||
|
||||
// Prepare initial filters
|
||||
let initialFilters = {
|
||||
start_date: startDateFilter.value,
|
||||
end_date: endDateFilter.value
|
||||
};
|
||||
|
||||
// Konfigurasi DataTable dengan filter awal
|
||||
const dataTableOptions = {
|
||||
apiEndpoint: apiUrl,
|
||||
pageSize: 10,
|
||||
searchParams: initialFilters, // Set filter awal di sini
|
||||
columns: {
|
||||
select: {
|
||||
render: (item, data, context) => {
|
||||
@@ -199,36 +195,26 @@
|
||||
return `<span class="text-gray-700">${formatPeriod(data.period)}</span>`;
|
||||
}
|
||||
},
|
||||
cleared_balance: {
|
||||
title: 'Saldo Cleared',
|
||||
render: (item, data) => {
|
||||
const amount = parseFloat(data.cleared_balance) || 0;
|
||||
return `<span class="text-blue-600 font-medium">${formatCurrency(amount)}</span>`;
|
||||
}
|
||||
},
|
||||
actual_balance: {
|
||||
title: 'Saldo Akhir',
|
||||
render: (item, data) => {
|
||||
const amount = parseFloat(data.actual_balance) || 0;
|
||||
return `<span class="text-green-600 font-medium">${formatCurrency(amount)}</span>`;
|
||||
}
|
||||
},
|
||||
updated_at: {
|
||||
title: 'Tanggal Update',
|
||||
render: (item, data) => {
|
||||
return `<span class="text-gray-600 text-sm">${formatDate(data.updated_at)}</span>`;
|
||||
return `<span class="text-sm text-gray-600">${formatDate(data.created_at)}</span>`;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
title: 'Action',
|
||||
render: (item, data) => {
|
||||
const downloadUrl =
|
||||
`{{ route('laporan-closing-balance.download', ['accountNumber' => '__ACCOUNT__', 'period' => '__PERIOD__']) }}`
|
||||
.replace('__ACCOUNT__', data.account_number)
|
||||
.replace('__PERIOD__', data.period);
|
||||
|
||||
return `<div class="flex flex-nowrap justify-center">
|
||||
<a class="btn btn-sm btn-icon btn-clear btn-info"
|
||||
href="{{ route('laporan-closing-balance.show', ['accountNumber' => '__ACCOUNT__', 'period' => '__PERIOD__']) }}"
|
||||
.replace('__ACCOUNT__', data.account_number)
|
||||
.replace('__PERIOD__', data.period)
|
||||
title="Lihat Detail">
|
||||
<i class="ki-outline ki-eye"></i>
|
||||
<a class="btn btn-sm btn-icon btn-clear btn-success"
|
||||
href="${downloadUrl}"
|
||||
title="Download Laporan"
|
||||
download>
|
||||
<i class="ki-outline ki-file-down"></i>
|
||||
</a>
|
||||
</div>`;
|
||||
},
|
||||
@@ -236,9 +222,11 @@
|
||||
},
|
||||
};
|
||||
|
||||
// Inisialisasi DataTable
|
||||
// Inisialisasi DataTable dengan filter awal sudah terset
|
||||
let dataTable = new KTDataTable(element, dataTableOptions);
|
||||
dataTable.showSpinner();
|
||||
|
||||
// Update export URL dengan filter awal
|
||||
updateExportUrl(initialFilters);
|
||||
|
||||
/**
|
||||
* Fungsi untuk menerapkan filter
|
||||
@@ -260,7 +248,7 @@
|
||||
|
||||
console.log('Applying filters:', filters);
|
||||
dataTable.search(filters);
|
||||
|
||||
|
||||
// Update export URL dengan filter
|
||||
updateExportUrl(filters);
|
||||
}
|
||||
@@ -272,7 +260,7 @@
|
||||
accountNumberFilter.value = '';
|
||||
startDateFilter.value = '';
|
||||
endDateFilter.value = '';
|
||||
|
||||
|
||||
dataTable.search({});
|
||||
updateExportUrl({});
|
||||
}
|
||||
@@ -281,15 +269,15 @@
|
||||
* Fungsi untuk update URL export dengan parameter filter
|
||||
*/
|
||||
function updateExportUrl(filters) {
|
||||
const baseUrl = '{{ route("laporan-closing-balance.export") }}';
|
||||
const baseUrl = '{{ route('laporan-closing-balance.export') }}';
|
||||
const params = new URLSearchParams();
|
||||
|
||||
|
||||
Object.keys(filters).forEach(key => {
|
||||
if (filters[key]) {
|
||||
params.append(key, filters[key]);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const newUrl = params.toString() ? `${baseUrl}?${params.toString()}` : baseUrl;
|
||||
exportBtn.href = newUrl;
|
||||
}
|
||||
@@ -297,7 +285,7 @@
|
||||
// Event listeners
|
||||
applyFilterBtn.addEventListener('click', applyFilters);
|
||||
resetFilterBtn.addEventListener('click', resetFilters);
|
||||
|
||||
|
||||
// Auto apply filter saat enter di input
|
||||
[accountNumberFilter, startDateFilter, endDateFilter].forEach(input => {
|
||||
input.addEventListener('keypress', function(e) {
|
||||
@@ -307,16 +295,9 @@
|
||||
});
|
||||
});
|
||||
|
||||
// Set default date range (last 30 days)
|
||||
const today = new Date();
|
||||
const thirtyDaysAgo = new Date(today.getTime() - (30 * 24 * 60 * 60 * 1000));
|
||||
|
||||
endDateFilter.value = today.toISOString().split('T')[0];
|
||||
startDateFilter.value = thirtyDaysAgo.toISOString().split('T')[0];
|
||||
|
||||
// Apply default filter
|
||||
setTimeout(() => {
|
||||
applyFilters();
|
||||
}, 100);
|
||||
// HAPUS bagian ini yang menyebabkan double API call:
|
||||
// setTimeout(() => {
|
||||
// applyFilters();
|
||||
// }, 100);
|
||||
</script>
|
||||
@endpush
|
||||
@endpush
|
||||
|
||||
@@ -130,6 +130,11 @@
|
||||
padding: 5px;
|
||||
text-align: left;
|
||||
font-size: 10px;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
white-space: normal;
|
||||
overflow-wrap: break-word;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
table th {
|
||||
@@ -278,7 +283,7 @@
|
||||
$totalDebit = 0;
|
||||
$totalKredit = 0;
|
||||
$line = 1;
|
||||
$linePerPage = 26;
|
||||
$linePerPage = 23;
|
||||
@endphp
|
||||
@php
|
||||
// Hitung tanggal periode berdasarkan $period
|
||||
|
||||
@@ -117,6 +117,7 @@ Route::middleware(['auth'])->group(function () {
|
||||
Route::group(['prefix' => 'laporan-closing-balance', 'as' => 'laporan-closing-balance.', 'middleware' => ['auth']], function () {
|
||||
Route::get('/datatables', [LaporanClosingBalanceController::class, 'dataForDatatables'])->name('datatables');
|
||||
Route::get('/export', [LaporanClosingBalanceController::class, 'export'])->name('export');
|
||||
Route::get('/{accountNumber}/{period}/download', [LaporanClosingBalanceController::class, 'download'])->name('download');
|
||||
Route::get('/{accountNumber}/{period}', [LaporanClosingBalanceController::class, 'show'])->name('show');
|
||||
});
|
||||
Route::resource('laporan-closing-balance', LaporanClosingBalanceController::class)->only(['index']);
|
||||
|
||||
Reference in New Issue
Block a user