Compare commits
28 Commits
master
...
291e791114
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
291e791114 | ||
|
|
00681a8e30 | ||
|
|
adda3122f8 | ||
|
|
e53b522f77 | ||
|
|
ffdb528360 | ||
|
|
1ff4035b98 | ||
|
|
f324f9e3f6 | ||
|
|
7af5bf2fe5 | ||
|
|
8a6469ecc9 | ||
|
|
aae0c4ab15 | ||
|
|
150d52f8da | ||
|
|
8736ccf5f8 | ||
|
|
710cbb5232 | ||
|
|
13e077073b | ||
|
|
eff951c600 | ||
|
|
6ad5aff358 | ||
|
|
bd72eb7dfa | ||
|
|
8eb7e69b21 | ||
|
|
4ee5c2e419 | ||
|
|
ca92f32ccb | ||
|
|
e1740c0850 | ||
|
|
d88f4a242e | ||
|
|
c0e5ddd37a | ||
|
|
5f9a82ec20 | ||
|
|
33b1255dfb | ||
|
|
aff6039b33 | ||
|
|
51e432c74f | ||
|
|
9cdc7f9487 |
@@ -1,51 +1,88 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Modules\Webstatement\Console;
|
namespace Modules\Webstatement\Console;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Modules\Webstatement\Http\Controllers\WebstatementController;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Modules\Webstatement\Http\Controllers\WebstatementController;
|
||||||
|
|
||||||
class ExportDailyStatements extends Command
|
/**
|
||||||
|
* Console command untuk export daily statements
|
||||||
|
* Command ini dapat dijalankan secara manual atau dijadwalkan
|
||||||
|
*/
|
||||||
|
class ExportDailyStatements extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'webstatement:export-statements
|
||||||
|
{--queue_name=default : Queue name untuk menjalankan export jobs (default: default)}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Export daily statements for all configured client accounts dengan queue name yang dapat dikustomisasi';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
* Menjalankan proses export daily statements
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
{
|
{
|
||||||
/**
|
$queueName = $this->option('queue_name');
|
||||||
* The name and signature of the console command.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $signature = 'webstatement:export-statements';
|
|
||||||
|
|
||||||
/**
|
// Log start of process
|
||||||
* The console command description.
|
Log::info('Starting daily statement export process', [
|
||||||
*
|
'queue_name' => $queueName ?? 'default',
|
||||||
* @var string
|
'command' => 'webstatement:export-statements'
|
||||||
*/
|
]);
|
||||||
protected $description = 'Export daily statements for all configured client accounts';
|
|
||||||
|
|
||||||
/**
|
$this->info('Starting daily statement export process...');
|
||||||
* Execute the console command.
|
$this->info('Queue Name: ' . ($queueName ?? 'default'));
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$this->info('Starting daily statement export process...');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$controller = app(WebstatementController::class);
|
$controller = app(WebstatementController::class);
|
||||||
$response = $controller->index();
|
|
||||||
|
|
||||||
$responseData = json_decode($response->getContent(), true);
|
// Pass queue name to controller if needed
|
||||||
$this->info($responseData['message']);
|
// Jika controller membutuhkan queue name, bisa ditambahkan sebagai parameter
|
||||||
|
$response = $controller->index($queueName);
|
||||||
|
|
||||||
// Display summary of jobs queued
|
$responseData = json_decode($response->getContent(), true);
|
||||||
$jobCount = count($responseData['jobs'] ?? []);
|
$message = $responseData['message'] ?? 'Export process completed';
|
||||||
$this->info("Successfully queued {$jobCount} statement export jobs");
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
$this->info($message);
|
||||||
} catch (Exception $e) {
|
|
||||||
$this->error('Error exporting statements: ' . $e->getMessage());
|
// Display summary of jobs queued
|
||||||
return Command::FAILURE;
|
$jobCount = count($responseData['jobs'] ?? []);
|
||||||
}
|
$this->info("Successfully queued {$jobCount} statement export jobs");
|
||||||
|
$this->info("Jobs dispatched to queue: {$queueName}");
|
||||||
|
|
||||||
|
// Log successful completion
|
||||||
|
Log::info('Daily statement export process completed successfully', [
|
||||||
|
'message' => $message,
|
||||||
|
'job_count' => $jobCount,
|
||||||
|
'queue_name' => $queueName ?? 'default'
|
||||||
|
]);
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$errorMessage = 'Error exporting statements: ' . $e->getMessage();
|
||||||
|
$this->error($errorMessage);
|
||||||
|
|
||||||
|
// Log error with queue information
|
||||||
|
Log::error($errorMessage, [
|
||||||
|
'exception' => $e->getTraceAsString(),
|
||||||
|
'queue_name' => $queueName ?? 'default'
|
||||||
|
]);
|
||||||
|
|
||||||
|
return Command::FAILURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ class GenerateClosingBalanceReportCommand extends Command
|
|||||||
protected $signature = 'webstatement:generate-closing-balance-report
|
protected $signature = 'webstatement:generate-closing-balance-report
|
||||||
{account_number : Nomor rekening untuk generate laporan}
|
{account_number : Nomor rekening untuk generate laporan}
|
||||||
{period : Period laporan format YYYYMMDD, contoh: 20250515}
|
{period : Period laporan format YYYYMMDD, contoh: 20250515}
|
||||||
|
{group=DEFAULT : Group transaksi QRIS atau DEFAULT}
|
||||||
{--user_id=1 : ID user yang menjalankan command (default: 1)}';
|
{--user_id=1 : ID user yang menjalankan command (default: 1)}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,7 +33,7 @@ class GenerateClosingBalanceReportCommand extends Command
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $description = 'Generate Closing Balance report untuk nomor rekening dan periode tertentu';
|
protected $description = 'Generate Closing Balance report untuk nomor rekening, periode, dan group tertentu';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the console command.
|
* Execute the console command.
|
||||||
@@ -47,10 +48,11 @@ class GenerateClosingBalanceReportCommand extends Command
|
|||||||
// Get parameters
|
// Get parameters
|
||||||
$accountNumber = $this->argument('account_number');
|
$accountNumber = $this->argument('account_number');
|
||||||
$period = $this->argument('period');
|
$period = $this->argument('period');
|
||||||
|
$group = $this->argument('group');
|
||||||
$userId = $this->option('user_id');
|
$userId = $this->option('user_id');
|
||||||
|
|
||||||
// Validate parameters
|
// Validate parameters
|
||||||
if (!$this->validateParameters($accountNumber, $period, $userId)) {
|
if (!$this->validateParameters($accountNumber, $period, $group, $userId)) {
|
||||||
return Command::FAILURE;
|
return Command::FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,12 +63,13 @@ class GenerateClosingBalanceReportCommand extends Command
|
|||||||
Log::info('Console command: Starting closing balance report generation', [
|
Log::info('Console command: Starting closing balance report generation', [
|
||||||
'account_number' => $accountNumber,
|
'account_number' => $accountNumber,
|
||||||
'period' => $period,
|
'period' => $period,
|
||||||
|
'group' => $group,
|
||||||
'user_id' => $userId,
|
'user_id' => $userId,
|
||||||
'command' => 'webstatement:generate-closing-balance-report'
|
'command' => 'webstatement:generate-closing-balance-report'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Create report log entry
|
// Create report log entry
|
||||||
$reportLog = $this->createReportLog($accountNumber, $period, $userId);
|
$reportLog = $this->createReportLog($accountNumber, $period, $group, $userId);
|
||||||
|
|
||||||
if (!$reportLog) {
|
if (!$reportLog) {
|
||||||
$this->error('Failed to create report log entry');
|
$this->error('Failed to create report log entry');
|
||||||
@@ -74,14 +77,15 @@ class GenerateClosingBalanceReportCommand extends Command
|
|||||||
return Command::FAILURE;
|
return Command::FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch the job
|
// Dispatch the job with group parameter
|
||||||
GenerateClosingBalanceReportJob::dispatch($accountNumber, $period, $reportLog->id);
|
GenerateClosingBalanceReportJob::dispatch($accountNumber, $period, $reportLog->id, $group);
|
||||||
|
|
||||||
DB::commit();
|
DB::commit();
|
||||||
|
|
||||||
$this->info("Closing Balance report generation job queued successfully!");
|
$this->info("Closing Balance report generation job queued successfully!");
|
||||||
$this->info("Account Number: {$accountNumber}");
|
$this->info("Account Number: {$accountNumber}");
|
||||||
$this->info("Period: {$period}");
|
$this->info("Period: {$period}");
|
||||||
|
$this->info("Group: {$group}");
|
||||||
$this->info("Report Log ID: {$reportLog->id}");
|
$this->info("Report Log ID: {$reportLog->id}");
|
||||||
$this->info('The report will be generated in the background.');
|
$this->info('The report will be generated in the background.');
|
||||||
$this->info('Check the closing_balance_report_logs table for progress.');
|
$this->info('Check the closing_balance_report_logs table for progress.');
|
||||||
@@ -90,6 +94,7 @@ class GenerateClosingBalanceReportCommand extends Command
|
|||||||
Log::info('Console command: Closing balance report job dispatched successfully', [
|
Log::info('Console command: Closing balance report job dispatched successfully', [
|
||||||
'account_number' => $accountNumber,
|
'account_number' => $accountNumber,
|
||||||
'period' => $period,
|
'period' => $period,
|
||||||
|
'group' => $group,
|
||||||
'report_log_id' => $reportLog->id,
|
'report_log_id' => $reportLog->id,
|
||||||
'user_id' => $userId
|
'user_id' => $userId
|
||||||
]);
|
]);
|
||||||
@@ -105,6 +110,7 @@ class GenerateClosingBalanceReportCommand extends Command
|
|||||||
Log::error('Console command: Error generating closing balance report', [
|
Log::error('Console command: Error generating closing balance report', [
|
||||||
'account_number' => $accountNumber,
|
'account_number' => $accountNumber,
|
||||||
'period' => $period,
|
'period' => $period,
|
||||||
|
'group' => $group,
|
||||||
'user_id' => $userId,
|
'user_id' => $userId,
|
||||||
'error' => $e->getMessage(),
|
'error' => $e->getMessage(),
|
||||||
'trace' => $e->getTraceAsString()
|
'trace' => $e->getTraceAsString()
|
||||||
@@ -120,10 +126,11 @@ class GenerateClosingBalanceReportCommand extends Command
|
|||||||
*
|
*
|
||||||
* @param string $accountNumber
|
* @param string $accountNumber
|
||||||
* @param string $period
|
* @param string $period
|
||||||
|
* @param string $group
|
||||||
* @param int $userId
|
* @param int $userId
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private function validateParameters(string $accountNumber, string $period, int $userId): bool
|
private function validateParameters(string $accountNumber, string $period, string $group, int $userId): bool
|
||||||
{
|
{
|
||||||
// Validate account number
|
// Validate account number
|
||||||
if (empty($accountNumber)) {
|
if (empty($accountNumber)) {
|
||||||
@@ -147,6 +154,13 @@ class GenerateClosingBalanceReportCommand extends Command
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate group parameter
|
||||||
|
$allowedGroups = ['QRIS', 'DEFAULT'];
|
||||||
|
if (!in_array(strtoupper($group), $allowedGroups)) {
|
||||||
|
$this->error('Invalid group parameter. Allowed values: ' . implode(', ', $allowedGroups));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Validate user exists
|
// Validate user exists
|
||||||
$user = User::find($userId);
|
$user = User::find($userId);
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
@@ -163,10 +177,11 @@ class GenerateClosingBalanceReportCommand extends Command
|
|||||||
*
|
*
|
||||||
* @param string $accountNumber
|
* @param string $accountNumber
|
||||||
* @param string $period
|
* @param string $period
|
||||||
|
* @param string $group
|
||||||
* @param int $userId
|
* @param int $userId
|
||||||
* @return ClosingBalanceReportLog|null
|
* @return ClosingBalanceReportLog|null
|
||||||
*/
|
*/
|
||||||
private function createReportLog(string $accountNumber, string $period, int $userId): ?ClosingBalanceReportLog
|
private function createReportLog(string $accountNumber, string $period, string $group, int $userId): ?ClosingBalanceReportLog
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// Convert period string to Carbon date
|
// Convert period string to Carbon date
|
||||||
@@ -176,6 +191,7 @@ class GenerateClosingBalanceReportCommand extends Command
|
|||||||
'account_number' => $accountNumber,
|
'account_number' => $accountNumber,
|
||||||
'period' => $period,
|
'period' => $period,
|
||||||
'report_date' => $reportDate, // Required field yang sebelumnya missing
|
'report_date' => $reportDate, // Required field yang sebelumnya missing
|
||||||
|
'group_name' => strtoupper($group), // Tambahkan group_name ke log
|
||||||
'status' => 'pending',
|
'status' => 'pending',
|
||||||
'user_id' => $userId,
|
'user_id' => $userId,
|
||||||
'created_by' => $userId, // Required field yang sebelumnya missing
|
'created_by' => $userId, // Required field yang sebelumnya missing
|
||||||
@@ -190,6 +206,7 @@ class GenerateClosingBalanceReportCommand extends Command
|
|||||||
'report_log_id' => $reportLog->id,
|
'report_log_id' => $reportLog->id,
|
||||||
'account_number' => $accountNumber,
|
'account_number' => $accountNumber,
|
||||||
'period' => $period,
|
'period' => $period,
|
||||||
|
'group' => $group,
|
||||||
'report_date' => $reportDate->format('Y-m-d'),
|
'report_date' => $reportDate->format('Y-m-d'),
|
||||||
'user_id' => $userId
|
'user_id' => $userId
|
||||||
]);
|
]);
|
||||||
@@ -200,6 +217,7 @@ class GenerateClosingBalanceReportCommand extends Command
|
|||||||
Log::error('Console command: Error creating report log', [
|
Log::error('Console command: Error creating report log', [
|
||||||
'account_number' => $accountNumber,
|
'account_number' => $accountNumber,
|
||||||
'period' => $period,
|
'period' => $period,
|
||||||
|
'group' => $group,
|
||||||
'user_id' => $userId,
|
'user_id' => $userId,
|
||||||
'error' => $e->getMessage(),
|
'error' => $e->getMessage(),
|
||||||
'trace' => $e->getTraceAsString()
|
'trace' => $e->getTraceAsString()
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Modules\Webstatement\Console;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Modules\Webstatement\Http\Controllers\MigrasiController;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
$processParameter = $this->option('process_parameter');
|
|
||||||
$period = $this->option('period');
|
|
||||||
|
|
||||||
// Log start of process
|
|
||||||
Log::info('Starting daily data migration process', [
|
|
||||||
'process_parameter' => $processParameter ?? 'false',
|
|
||||||
'period' => $period ?? '-1 day'
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->info('Starting daily data migration process...');
|
|
||||||
$this->info('Process Parameter: ' . ($processParameter ?? 'False'));
|
|
||||||
$this->info('Period: ' . ($period ?? '-1 day (default)'));
|
|
||||||
|
|
||||||
try {
|
|
||||||
$controller = app(MigrasiController::class);
|
|
||||||
$response = $controller->index($processParameter, $period);
|
|
||||||
|
|
||||||
$responseData = json_decode($response->getContent(), true);
|
|
||||||
$message = $responseData['message'] ?? 'Process completed';
|
|
||||||
|
|
||||||
$this->info($message);
|
|
||||||
Log::info('Daily migration process completed successfully', ['message' => $message]);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
85
app/Console/ProcessDailyStaging.php
Normal file
85
app/Console/ProcessDailyStaging.php
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Webstatement\Console;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Modules\Webstatement\Http\Controllers\StagingController;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Console command untuk memproses data staging harian
|
||||||
|
* Command ini dapat dijalankan secara manual atau dijadwalkan
|
||||||
|
*/
|
||||||
|
class ProcessDailyStaging extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'webstatement:process-daily-staging
|
||||||
|
{--process_parameter= : To process staging parameter true/false}
|
||||||
|
{--period= : Period to process (default: -1 day, format: Ymd or relative date)}
|
||||||
|
{--queue_name=default : Queue name untuk menjalankan job (default: default)}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Process data staging for the specified period (default: previous day) dengan queue name yang dapat dikustomisasi';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
* Menjalankan proses staging data harian
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$processParameter = $this->option('process_parameter');
|
||||||
|
$period = $this->option('period');
|
||||||
|
$queueName = $this->option('queue_name');
|
||||||
|
|
||||||
|
// Log start of process
|
||||||
|
Log::info('Starting daily data staging process', [
|
||||||
|
'process_parameter' => $processParameter ?? 'false',
|
||||||
|
'period' => $period ?? '-1 day',
|
||||||
|
'queue_name' => $queueName ?? 'default'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->info('Starting daily data staging process...');
|
||||||
|
$this->info('Process Parameter: ' . ($processParameter ?? 'False'));
|
||||||
|
$this->info('Period: ' . ($period ?? '-1 day (default)'));
|
||||||
|
$this->info('Queue Name: ' . ($queueName ?? 'default'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
$controller = app(StagingController::class);
|
||||||
|
|
||||||
|
// Pass queue name to controller if needed
|
||||||
|
// Jika controller membutuhkan queue name, bisa ditambahkan sebagai parameter
|
||||||
|
$response = $controller->index($processParameter, $period, $queueName);
|
||||||
|
|
||||||
|
$responseData = json_decode($response->getContent(), true);
|
||||||
|
$message = $responseData['message'] ?? 'Process completed';
|
||||||
|
|
||||||
|
$this->info($message);
|
||||||
|
Log::info('Daily staging process completed successfully', [
|
||||||
|
'message' => $message,
|
||||||
|
'queue_name' => $queueName ?? 'default'
|
||||||
|
]);
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$errorMessage = 'Error processing daily staging: ' . $e->getMessage();
|
||||||
|
$this->error($errorMessage);
|
||||||
|
Log::error($errorMessage, [
|
||||||
|
'exception' => $e->getTraceAsString(),
|
||||||
|
'queue_name' => $queueName ?? 'default'
|
||||||
|
]);
|
||||||
|
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
140
app/Enums/ResponseCode.php
Normal file
140
app/Enums/ResponseCode.php
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Webstatement\Enums;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response Code Enum untuk standarisasi response API
|
||||||
|
*
|
||||||
|
* @category Enums
|
||||||
|
* @package Modules\Webstatement\Enums
|
||||||
|
*/
|
||||||
|
enum ResponseCode: string
|
||||||
|
{
|
||||||
|
// Success Codes
|
||||||
|
case SUCCESS = '00';
|
||||||
|
|
||||||
|
// Data Error Codes
|
||||||
|
case INVALID_FIELD = '01';
|
||||||
|
case MISSING_FIELD = '02';
|
||||||
|
case INVALID_FORMAT = '03';
|
||||||
|
case DATA_NOT_FOUND = '04';
|
||||||
|
case DUPLICATE_REQUEST = '05';
|
||||||
|
case ACCOUNT_ALREADY_EXISTS = '06';
|
||||||
|
case ACCOUNT_NOT_FOUND = '07';
|
||||||
|
|
||||||
|
// Auth Error Codes
|
||||||
|
case INVALID_TOKEN = '10';
|
||||||
|
case UNAUTHORIZED = '11';
|
||||||
|
|
||||||
|
// System Error Codes
|
||||||
|
case SYSTEM_MALFUNCTION = '96';
|
||||||
|
case TIMEOUT = '97';
|
||||||
|
case SERVICE_UNAVAILABLE = '98';
|
||||||
|
case GENERAL_ERROR = '99';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mendapatkan pesan response berdasarkan kode
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getMessage(): string
|
||||||
|
{
|
||||||
|
return match($this) {
|
||||||
|
self::SUCCESS => 'Success',
|
||||||
|
self::INVALID_FIELD => 'Invalid Field',
|
||||||
|
self::MISSING_FIELD => 'Missing Field',
|
||||||
|
self::INVALID_FORMAT => 'Invalid Format',
|
||||||
|
self::DATA_NOT_FOUND => 'Data Not Found',
|
||||||
|
self::DUPLICATE_REQUEST => 'Duplicate Request',
|
||||||
|
self::ACCOUNT_ALREADY_EXISTS => 'Account Already Exists',
|
||||||
|
self::ACCOUNT_NOT_FOUND => 'Account Not Found',
|
||||||
|
self::INVALID_TOKEN => 'Invalid Token',
|
||||||
|
self::UNAUTHORIZED => 'Unauthorized',
|
||||||
|
self::SYSTEM_MALFUNCTION => 'System Malfunction',
|
||||||
|
self::TIMEOUT => 'Timeout',
|
||||||
|
self::SERVICE_UNAVAILABLE => 'Service Unavailable',
|
||||||
|
self::GENERAL_ERROR => 'General Error',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mendapatkan deskripsi response berdasarkan kode
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return match($this) {
|
||||||
|
self::SUCCESS => 'Permintaan berhasil',
|
||||||
|
self::INVALID_FIELD => 'Field tertentu tidak sesuai aturan',
|
||||||
|
self::MISSING_FIELD => 'Field wajib tidak dikirim',
|
||||||
|
self::INVALID_FORMAT => 'Format salah',
|
||||||
|
self::DATA_NOT_FOUND => 'Data yang diminta tidak ditemukan',
|
||||||
|
self::DUPLICATE_REQUEST => 'Request ID sama, sudah pernah diproses',
|
||||||
|
self::ACCOUNT_ALREADY_EXISTS => 'Nomor rekening / username / email sudah terdaftar',
|
||||||
|
self::ACCOUNT_NOT_FOUND => 'Nomor rekening / akun tidak ditemukan',
|
||||||
|
self::INVALID_TOKEN => 'Token tidak valid',
|
||||||
|
self::UNAUTHORIZED => 'Tidak punya akses',
|
||||||
|
self::SYSTEM_MALFUNCTION => 'Gangguan teknis di server',
|
||||||
|
self::TIMEOUT => 'Request timeout',
|
||||||
|
self::SERVICE_UNAVAILABLE => 'Layanan tidak tersedia',
|
||||||
|
self::GENERAL_ERROR => 'Kesalahan umum',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mendapatkan HTTP status code berdasarkan response code
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getHttpStatus(): int
|
||||||
|
{
|
||||||
|
return match($this) {
|
||||||
|
self::SUCCESS => 200,
|
||||||
|
self::INVALID_FIELD,
|
||||||
|
self::MISSING_FIELD,
|
||||||
|
self::INVALID_FORMAT => 400,
|
||||||
|
self::DATA_NOT_FOUND,
|
||||||
|
self::ACCOUNT_NOT_FOUND => 404,
|
||||||
|
self::DUPLICATE_REQUEST,
|
||||||
|
self::ACCOUNT_ALREADY_EXISTS => 409,
|
||||||
|
self::INVALID_TOKEN,
|
||||||
|
self::UNAUTHORIZED => 401,
|
||||||
|
self::SYSTEM_MALFUNCTION,
|
||||||
|
self::GENERAL_ERROR => 500,
|
||||||
|
self::TIMEOUT => 408,
|
||||||
|
self::SERVICE_UNAVAILABLE => 503,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Membuat response array standar
|
||||||
|
*
|
||||||
|
* @param mixed $data
|
||||||
|
* @param string|null $message
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function toResponse($data = null, ?string $message = null): array
|
||||||
|
{
|
||||||
|
$response = [
|
||||||
|
'status' => $this->value == '00' ? true : false,
|
||||||
|
'response_code' => $this->value,
|
||||||
|
'response_message' => $this->getMessage() . ($message ? ' | ' . $message : ''),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isset($data['errors'])) {
|
||||||
|
$response['errors'] = $data['errors'];
|
||||||
|
} else {
|
||||||
|
$response['data'] = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$response['meta'] = [
|
||||||
|
'generated_at' => now()->toDateTimeString(),
|
||||||
|
'request_id' => request()->header('X-Request-ID', uniqid('req_'))
|
||||||
|
];
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
97
app/Http/Controllers/Api/AccountBalanceController.php
Normal file
97
app/Http/Controllers/Api/AccountBalanceController.php
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Webstatement\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Modules\Webstatement\Http\Requests\BalanceSummaryRequest;
|
||||||
|
use Modules\Webstatement\Http\Requests\DetailedBalanceRequest;
|
||||||
|
use Modules\Webstatement\Services\AccountBalanceService;
|
||||||
|
use Modules\Webstatement\Http\Resources\BalanceSummaryResource;
|
||||||
|
use Modules\Webstatement\Http\Resources\DetailedBalanceResource;
|
||||||
|
use Modules\Webstatement\Enums\ResponseCode;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class AccountBalanceController extends Controller
|
||||||
|
{
|
||||||
|
protected AccountBalanceService $accountBalanceService;
|
||||||
|
|
||||||
|
public function __construct(AccountBalanceService $accountBalanceService)
|
||||||
|
{
|
||||||
|
$this->accountBalanceService = $accountBalanceService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get account balance summary (opening and closing balance)
|
||||||
|
*
|
||||||
|
* @param BalanceSummaryRequest $request
|
||||||
|
* @return JsonResponse
|
||||||
|
*/
|
||||||
|
public function getBalanceSummary(BalanceSummaryRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$accountNumber = $request->input('account_number');
|
||||||
|
$startDate = $request->input('start_date');
|
||||||
|
$endDate = $request->input('end_date');
|
||||||
|
|
||||||
|
Log::info('Account balance summary requested', [
|
||||||
|
'account_number' => $accountNumber,
|
||||||
|
'start_date' => $startDate,
|
||||||
|
'end_date' => $endDate,
|
||||||
|
'ip' => $request->ip(),
|
||||||
|
'user_agent' => $request->userAgent()
|
||||||
|
]);
|
||||||
|
|
||||||
|
$result = $this->accountBalanceService->getBalanceSummary(
|
||||||
|
$accountNumber,
|
||||||
|
$startDate,
|
||||||
|
$endDate
|
||||||
|
);
|
||||||
|
|
||||||
|
if (empty($result)) {
|
||||||
|
return response()->json(
|
||||||
|
ResponseCode::DATA_NOT_FOUND->toResponse(
|
||||||
|
null,
|
||||||
|
'Rekening tidak ditemukan'
|
||||||
|
),
|
||||||
|
ResponseCode::DATA_NOT_FOUND->getHttpStatus()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
ResponseCode::SUCCESS->toResponse(
|
||||||
|
(new BalanceSummaryResource($result))->toArray($request),
|
||||||
|
|
||||||
|
),
|
||||||
|
ResponseCode::SUCCESS->getHttpStatus()
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::error('Error getting account balance summary', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'trace' => $e->getTraceAsString()
|
||||||
|
]);
|
||||||
|
|
||||||
|
$responseCode = match ($e->getCode()) {
|
||||||
|
404 => ResponseCode::DATA_NOT_FOUND,
|
||||||
|
401 => ResponseCode::UNAUTHORIZED,
|
||||||
|
403 => ResponseCode::UNAUTHORIZED,
|
||||||
|
408 => ResponseCode::TIMEOUT,
|
||||||
|
503 => ResponseCode::SERVICE_UNAVAILABLE,
|
||||||
|
400 => ResponseCode::INVALID_FIELD,
|
||||||
|
default => ResponseCode::SYSTEM_MALFUNCTION
|
||||||
|
};
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
$responseCode->toResponse(
|
||||||
|
null,
|
||||||
|
config('app.debug') ? $e->getMessage() : 'Terjadi kesalahan sistem'
|
||||||
|
),
|
||||||
|
$responseCode->getHttpStatus()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Modules\Webstatement\Http\Controllers;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use BadMethodCallException;
|
|
||||||
use Exception;
|
|
||||||
use Illuminate\Http\JsonResponse;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Modules\Webstatement\Jobs\{ProcessAccountDataJob,
|
|
||||||
ProcessArrangementDataJob,
|
|
||||||
ProcessAtmTransactionJob,
|
|
||||||
ProcessBillDetailDataJob,
|
|
||||||
ProcessCategoryDataJob,
|
|
||||||
ProcessCompanyDataJob,
|
|
||||||
ProcessCustomerDataJob,
|
|
||||||
ProcessDataCaptureDataJob,
|
|
||||||
ProcessFtTxnTypeConditionJob,
|
|
||||||
ProcessFundsTransferDataJob,
|
|
||||||
ProcessStmtEntryDataJob,
|
|
||||||
ProcessStmtNarrFormatDataJob,
|
|
||||||
ProcessStmtNarrParamDataJob,
|
|
||||||
ProcessTellerDataJob,
|
|
||||||
ProcessTransactionDataJob,
|
|
||||||
ProcessSectorDataJob,
|
|
||||||
ProcessProvinceDataJob,
|
|
||||||
ProcessStmtEntryDetailDataJob};
|
|
||||||
|
|
||||||
class MigrasiController extends Controller
|
|
||||||
{
|
|
||||||
private const PROCESS_TYPES = [
|
|
||||||
'transaction' => ProcessTransactionDataJob::class,
|
|
||||||
'stmtNarrParam' => ProcessStmtNarrParamDataJob::class,
|
|
||||||
'stmtNarrFormat' => ProcessStmtNarrFormatDataJob::class,
|
|
||||||
'ftTxnTypeCondition' => ProcessFtTxnTypeConditionJob::class,
|
|
||||||
'category' => ProcessCategoryDataJob::class,
|
|
||||||
'company' => ProcessCompanyDataJob::class,
|
|
||||||
'customer' => ProcessCustomerDataJob::class,
|
|
||||||
'account' => ProcessAccountDataJob::class,
|
|
||||||
'stmtEntry' => ProcessStmtEntryDataJob::class,
|
|
||||||
'stmtEntryDetail' => ProcessStmtEntryDetailDataJob::class, // Tambahan baru
|
|
||||||
'dataCapture' => ProcessDataCaptureDataJob::class,
|
|
||||||
'fundsTransfer' => ProcessFundsTransferDataJob::class,
|
|
||||||
'teller' => ProcessTellerDataJob::class,
|
|
||||||
'atmTransaction' => ProcessAtmTransactionJob::class,
|
|
||||||
'arrangement' => ProcessArrangementDataJob::class,
|
|
||||||
'billDetail' => ProcessBillDetailDataJob::class,
|
|
||||||
'sector' => ProcessSectorDataJob::class,
|
|
||||||
'province' => ProcessProvinceDataJob::class
|
|
||||||
];
|
|
||||||
|
|
||||||
private const PARAMETER_PROCESSES = [
|
|
||||||
'transaction',
|
|
||||||
'stmtNarrParam',
|
|
||||||
'stmtNarrFormat',
|
|
||||||
'ftTxnTypeCondition',
|
|
||||||
'sector',
|
|
||||||
'province'
|
|
||||||
];
|
|
||||||
|
|
||||||
private const DATA_PROCESSES = [
|
|
||||||
'category',
|
|
||||||
'company',
|
|
||||||
'customer',
|
|
||||||
'account',
|
|
||||||
'stmtEntry',
|
|
||||||
'stmtEntryDetail', // Tambahan baru
|
|
||||||
'dataCapture',
|
|
||||||
'fundsTransfer',
|
|
||||||
'teller',
|
|
||||||
'atmTransaction',
|
|
||||||
'arrangement',
|
|
||||||
'billDetail'
|
|
||||||
];
|
|
||||||
|
|
||||||
public function __call($method, $parameters)
|
|
||||||
{
|
|
||||||
if (strpos($method, 'process') === 0) {
|
|
||||||
$type = lcfirst(substr($method, 7));
|
|
||||||
if (isset(self::PROCESS_TYPES[$type])) {
|
|
||||||
return $this->processData($type, $parameters[0] ?? '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new BadMethodCallException("Method {$method} does not exist.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private function processData(string $type, string $period)
|
|
||||||
: JsonResponse
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$jobClass = self::PROCESS_TYPES[$type];
|
|
||||||
$jobClass::dispatch($period);
|
|
||||||
|
|
||||||
$message = sprintf('%s data processing job has been queued successfully', ucfirst($type));
|
|
||||||
Log::info($message);
|
|
||||||
|
|
||||||
return response()->json(['message' => $message]);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
Log::error(sprintf('Error in %s processing: %s', $type, $e->getMessage()));
|
|
||||||
return response()->json(['error' => $e->getMessage()], 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 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)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
Log::info('Starting migration process', [
|
|
||||||
'process_parameter' => $processParameter,
|
|
||||||
'period' => $period
|
|
||||||
]);
|
|
||||||
|
|
||||||
$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']);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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' => $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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
240
app/Http/Controllers/StagingController.php
Normal file
240
app/Http/Controllers/StagingController.php
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Webstatement\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use BadMethodCallException;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Modules\Webstatement\Jobs\{ProcessAccountDataJob,
|
||||||
|
ProcessArrangementDataJob,
|
||||||
|
ProcessAtmTransactionJob,
|
||||||
|
ProcessBillDetailDataJob,
|
||||||
|
ProcessCategoryDataJob,
|
||||||
|
ProcessCompanyDataJob,
|
||||||
|
ProcessCustomerDataJob,
|
||||||
|
ProcessDataCaptureDataJob,
|
||||||
|
ProcessFtTxnTypeConditionJob,
|
||||||
|
ProcessFundsTransferDataJob,
|
||||||
|
ProcessStmtEntryDataJob,
|
||||||
|
ProcessStmtNarrFormatDataJob,
|
||||||
|
ProcessStmtNarrParamDataJob,
|
||||||
|
ProcessTellerDataJob,
|
||||||
|
ProcessTransactionDataJob,
|
||||||
|
ProcessSectorDataJob,
|
||||||
|
ProcessProvinceDataJob,
|
||||||
|
ProcessStmtEntryDetailDataJob};
|
||||||
|
|
||||||
|
class StagingController extends Controller
|
||||||
|
{
|
||||||
|
private const PROCESS_TYPES = [
|
||||||
|
'transaction' => ProcessTransactionDataJob::class,
|
||||||
|
'stmtNarrParam' => ProcessStmtNarrParamDataJob::class,
|
||||||
|
'stmtNarrFormat' => ProcessStmtNarrFormatDataJob::class,
|
||||||
|
'ftTxnTypeCondition' => ProcessFtTxnTypeConditionJob::class,
|
||||||
|
'category' => ProcessCategoryDataJob::class,
|
||||||
|
'company' => ProcessCompanyDataJob::class,
|
||||||
|
'customer' => ProcessCustomerDataJob::class,
|
||||||
|
'account' => ProcessAccountDataJob::class,
|
||||||
|
'stmtEntry' => ProcessStmtEntryDataJob::class,
|
||||||
|
'stmtEntryDetail' => ProcessStmtEntryDetailDataJob::class,
|
||||||
|
'dataCapture' => ProcessDataCaptureDataJob::class,
|
||||||
|
'fundsTransfer' => ProcessFundsTransferDataJob::class,
|
||||||
|
'teller' => ProcessTellerDataJob::class,
|
||||||
|
'atmTransaction' => ProcessAtmTransactionJob::class,
|
||||||
|
'arrangement' => ProcessArrangementDataJob::class,
|
||||||
|
'billDetail' => ProcessBillDetailDataJob::class,
|
||||||
|
'sector' => ProcessSectorDataJob::class,
|
||||||
|
'province' => ProcessProvinceDataJob::class
|
||||||
|
];
|
||||||
|
|
||||||
|
private const PARAMETER_PROCESSES = [
|
||||||
|
'transaction',
|
||||||
|
'stmtNarrParam',
|
||||||
|
'stmtNarrFormat',
|
||||||
|
'ftTxnTypeCondition',
|
||||||
|
'sector',
|
||||||
|
'province'
|
||||||
|
];
|
||||||
|
|
||||||
|
private const DATA_PROCESSES = [
|
||||||
|
'category',
|
||||||
|
'company',
|
||||||
|
'customer',
|
||||||
|
'account',
|
||||||
|
'stmtEntry',
|
||||||
|
'stmtEntryDetail',
|
||||||
|
'dataCapture',
|
||||||
|
'fundsTransfer',
|
||||||
|
'teller',
|
||||||
|
'atmTransaction',
|
||||||
|
'arrangement',
|
||||||
|
'billDetail'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __call($method, $parameters)
|
||||||
|
{
|
||||||
|
if (strpos($method, 'process') === 0) {
|
||||||
|
$type = lcfirst(substr($method, 7));
|
||||||
|
if (isset(self::PROCESS_TYPES[$type])) {
|
||||||
|
return $this->processData($type, $parameters[0] ?? '', $parameters[1] ?? 'default');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new BadMethodCallException("Method {$method} does not exist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memproses data dengan queue name yang dapat dikustomisasi
|
||||||
|
*
|
||||||
|
* @param string $type Tipe proses yang akan dijalankan
|
||||||
|
* @param string $period Periode data yang akan diproses
|
||||||
|
* @param string $queueName Nama queue untuk menjalankan job
|
||||||
|
* @return JsonResponse
|
||||||
|
*/
|
||||||
|
private function processData(string $type, string $period, string $queueName = 'default'): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$jobClass = self::PROCESS_TYPES[$type];
|
||||||
|
|
||||||
|
// Dispatch job dengan queue name yang spesifik
|
||||||
|
$jobClass::dispatch($period)->onQueue($queueName);
|
||||||
|
|
||||||
|
$message = sprintf('%s data processing job has been queued successfully on queue: %s', ucfirst($type), $queueName);
|
||||||
|
Log::info($message, [
|
||||||
|
'type' => $type,
|
||||||
|
'period' => $period,
|
||||||
|
'queue_name' => $queueName
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => $message,
|
||||||
|
'queue_name' => $queueName
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::error(sprintf('Error in %s processing: %s', $type, $e->getMessage()), [
|
||||||
|
'type' => $type,
|
||||||
|
'period' => $period,
|
||||||
|
'queue_name' => $queueName,
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
return response()->json(['error' => $e->getMessage()], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proses migrasi data dengan parameter, periode, dan queue name yang dapat dikustomisasi
|
||||||
|
*
|
||||||
|
* @param bool|string $processParameter Flag untuk memproses parameter
|
||||||
|
* @param string|null $period Periode yang akan diproses (default: -1 day)
|
||||||
|
* @param string $queueName Nama queue untuk menjalankan job (default: default)
|
||||||
|
* @return JsonResponse
|
||||||
|
*/
|
||||||
|
public function index($processParameter = false, $period = null, $queueName = 'default')
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Log::info('Starting migration process', [
|
||||||
|
'process_parameter' => $processParameter,
|
||||||
|
'period' => $period,
|
||||||
|
'queue_name' => $queueName
|
||||||
|
]);
|
||||||
|
|
||||||
|
$disk = Storage::disk('staging');
|
||||||
|
|
||||||
|
if ($processParameter) {
|
||||||
|
Log::info('Processing parameter data', ['queue_name' => $queueName]);
|
||||||
|
|
||||||
|
foreach (self::PARAMETER_PROCESSES as $process) {
|
||||||
|
$this->processData($process, '_parameter', $queueName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info('Parameter processes completed successfully', ['queue_name' => $queueName]);
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Parameter processes completed successfully',
|
||||||
|
'queue_name' => $queueName
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tentukan periode yang akan diproses
|
||||||
|
$targetPeriod = $this->determinePeriod($period);
|
||||||
|
|
||||||
|
Log::info('Processing data for period', [
|
||||||
|
'period' => $targetPeriod,
|
||||||
|
'queue_name' => $queueName
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$disk->exists($targetPeriod)) {
|
||||||
|
$errorMessage = "Period {$targetPeriod} folder not found in SFTP storage";
|
||||||
|
Log::warning($errorMessage, ['queue_name' => $queueName]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
"message" => $errorMessage,
|
||||||
|
'queue_name' => $queueName
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (self::DATA_PROCESSES as $process) {
|
||||||
|
$this->processData($process, $targetPeriod, $queueName);
|
||||||
|
}
|
||||||
|
|
||||||
|
$successMessage = "Data processing for period {$targetPeriod} has been queued successfully on queue: {$queueName}";
|
||||||
|
Log::info($successMessage, [
|
||||||
|
'period' => $targetPeriod,
|
||||||
|
'queue_name' => $queueName
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => $successMessage,
|
||||||
|
'queue_name' => $queueName
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::error('Error in migration index method: ' . $e->getMessage(), [
|
||||||
|
'queue_name' => $queueName,
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
return response()->json([
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'queue_name' => $queueName
|
||||||
|
], 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,204 +1,246 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Modules\Webstatement\Http\Controllers;
|
namespace Modules\Webstatement\Http\Controllers;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Contracts\Bus\Dispatcher;
|
use Illuminate\Contracts\Bus\Dispatcher;
|
||||||
use Modules\Webstatement\Jobs\ExportStatementJob;
|
use Illuminate\Http\Request;
|
||||||
use Modules\Webstatement\Models\AccountBalance;
|
use Modules\Webstatement\Jobs\ExportStatementJob;
|
||||||
use Modules\Webstatement\Jobs\ExportStatementPeriodJob;
|
use Modules\Webstatement\Models\AccountBalance;
|
||||||
|
use Modules\Webstatement\Jobs\ExportStatementPeriodJob;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class WebstatementController extends Controller
|
class WebstatementController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
* Menjalankan export statement untuk semua akun dengan queue name yang dapat dikustomisasi
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
/**
|
$queueName = $request->get('queue_name', 'default');
|
||||||
* Display a listing of the resource.
|
|
||||||
*/
|
|
||||||
public function index()
|
|
||||||
{
|
|
||||||
$jobIds = [];
|
|
||||||
$data = [];
|
|
||||||
|
|
||||||
foreach ($this->listAccount() as $clientName => $accounts) {
|
Log::info('Starting statement export process', [
|
||||||
foreach ($accounts as $accountNumber) {
|
'queue_name' => $queueName
|
||||||
foreach ($this->listPeriod() as $period) {
|
]);
|
||||||
$job = new ExportStatementJob(
|
|
||||||
$accountNumber,
|
$jobIds = [];
|
||||||
$period,
|
$data = [];
|
||||||
$this->getAccountBalance($accountNumber, $period),
|
|
||||||
$clientName // Pass the client name to the job
|
foreach ($this->listAccount() as $clientName => $accounts) {
|
||||||
);
|
foreach ($accounts as $accountNumber) {
|
||||||
$jobIds[] = app(Dispatcher::class)->dispatch($job);
|
foreach ($this->listPeriod() as $period) {
|
||||||
$data[] = [
|
$job = new ExportStatementJob(
|
||||||
'client_name' => $clientName,
|
$accountNumber,
|
||||||
'account_number' => $accountNumber,
|
$period,
|
||||||
'period' => $period
|
$this->getAccountBalance($accountNumber, $period),
|
||||||
];
|
$clientName // Pass the client name to the job
|
||||||
}
|
);
|
||||||
|
|
||||||
|
// Dispatch job dengan queue name yang spesifik
|
||||||
|
$jobIds[] = app(Dispatcher::class)->dispatch($job->onQueue($queueName));
|
||||||
|
$data[] = [
|
||||||
|
'client_name' => $clientName,
|
||||||
|
'account_number' => $accountNumber,
|
||||||
|
'period' => $period,
|
||||||
|
'queue_name' => $queueName
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info('Statement export jobs queued successfully', [
|
||||||
|
'total_jobs' => count($jobIds),
|
||||||
|
'queue_name' => $queueName
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Statement export jobs have been queued',
|
||||||
|
'queue_name' => $queueName,
|
||||||
|
'jobs' => array_map(function ($index, $jobId) use ($data) {
|
||||||
|
return [
|
||||||
|
'job_id' => $jobId,
|
||||||
|
'client_name' => $data[$index]['client_name'],
|
||||||
|
'account_number' => $data[$index]['account_number'],
|
||||||
|
'period' => $data[$index]['period'],
|
||||||
|
'queue_name' => $data[$index]['queue_name'],
|
||||||
|
'file_name' => "{$data[$index]['client_name']}_{$data[$index]['account_number']}_{$data[$index]['period']}.csv"
|
||||||
|
];
|
||||||
|
}, array_keys($jobIds), $jobIds)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function listAccount(){
|
||||||
|
return [
|
||||||
|
'PLUANG' => [
|
||||||
|
'1080426085',
|
||||||
|
'1080425781',
|
||||||
|
],
|
||||||
|
'OY' => [
|
||||||
|
'1081647484',
|
||||||
|
'1081647485',
|
||||||
|
],
|
||||||
|
'INDORAYA' => [
|
||||||
|
'1083123710',
|
||||||
|
'1083123711',
|
||||||
|
'1083123712',
|
||||||
|
'1083123713',
|
||||||
|
'1083123714',
|
||||||
|
'1083123715',
|
||||||
|
'1083123716',
|
||||||
|
'1083123718',
|
||||||
|
'1083123719',
|
||||||
|
'1083123721',
|
||||||
|
'1083123722',
|
||||||
|
'1083123723',
|
||||||
|
'1083123724',
|
||||||
|
'1083123726',
|
||||||
|
'1083123727',
|
||||||
|
'1083123728',
|
||||||
|
'1083123730',
|
||||||
|
'1083123731',
|
||||||
|
'1083123732',
|
||||||
|
'1083123734',
|
||||||
|
'1083123735',
|
||||||
|
],
|
||||||
|
'TDC' => [
|
||||||
|
'1086677889',
|
||||||
|
'1086677890',
|
||||||
|
'1086677891',
|
||||||
|
'1086677892',
|
||||||
|
'1086677893',
|
||||||
|
'1086677894',
|
||||||
|
'1086677895',
|
||||||
|
'1086677896',
|
||||||
|
'1086677897',
|
||||||
|
],
|
||||||
|
'ASIA_PARKING' => [
|
||||||
|
'1080119298',
|
||||||
|
'1080119361',
|
||||||
|
'1080119425',
|
||||||
|
'1080119387',
|
||||||
|
'1082208069',
|
||||||
|
],
|
||||||
|
'DAU' => [
|
||||||
|
'1085151668',
|
||||||
|
],
|
||||||
|
'EGR' => [
|
||||||
|
'1085368601',
|
||||||
|
],
|
||||||
|
'SARANA_PACTINDO' => [
|
||||||
|
'1078333878',
|
||||||
|
],
|
||||||
|
'SWADAYA_PANDU' => [
|
||||||
|
'0081272689',
|
||||||
|
],
|
||||||
|
"AWAN_LINTANG_SOLUSI"=> [
|
||||||
|
"1084269430"
|
||||||
|
],
|
||||||
|
"MONETA"=> [
|
||||||
|
"1085667890"
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function listPeriod(){
|
||||||
|
return [
|
||||||
|
date('Ymd', strtotime('-1 day'))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getAccountBalance($accountNumber, $period)
|
||||||
|
{
|
||||||
|
$accountBalance = AccountBalance::where('account_number', $accountNumber)
|
||||||
|
->where('period', '<', $period)
|
||||||
|
->orderBy('period', 'desc')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
return $accountBalance->actual_balance ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print statement rekening dengan queue name yang dapat dikustomisasi
|
||||||
|
*
|
||||||
|
* @param string $accountNumber
|
||||||
|
* @param string|null $period
|
||||||
|
* @param Request $request
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
function printStatementRekening($accountNumber, $period = null, Request $request = null) {
|
||||||
|
$queueName = $request ? $request->get('queue_name', 'default') : 'default';
|
||||||
|
$period = $period ?? date('Ym');
|
||||||
|
|
||||||
|
$balance = AccountBalance::where('account_number', $accountNumber)
|
||||||
|
->when($period === '202505', function($query) {
|
||||||
|
return $query->where('period', '>=', '20250512')
|
||||||
|
->orderBy('period', 'asc');
|
||||||
|
}, function($query) use ($period) {
|
||||||
|
// Get balance from last day of previous month
|
||||||
|
$firstDayOfMonth = Carbon::createFromFormat('Ym', $period)->startOfMonth();
|
||||||
|
$lastDayPrevMonth = $firstDayOfMonth->copy()->subDay()->format('Ymd');
|
||||||
|
return $query->where('period', $lastDayPrevMonth);
|
||||||
|
})
|
||||||
|
->first()
|
||||||
|
->actual_balance ?? '0.00';
|
||||||
|
$clientName = 'client1';
|
||||||
|
|
||||||
|
try {
|
||||||
|
Log::info("Starting statement export for account: {$accountNumber}, period: {$period}, client: {$clientName}", [
|
||||||
|
'account_number' => $accountNumber,
|
||||||
|
'period' => $period,
|
||||||
|
'client_name' => $clientName,
|
||||||
|
'queue_name' => $queueName
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Validate inputs
|
||||||
|
if (empty($accountNumber) || empty($period) || empty($clientName)) {
|
||||||
|
throw new \Exception('Required parameters missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch the job dengan queue name yang spesifik
|
||||||
|
$job = ExportStatementPeriodJob::dispatch($accountNumber, $period, $balance, $clientName)
|
||||||
|
->onQueue($queueName);
|
||||||
|
|
||||||
|
Log::info("Statement export job dispatched successfully", [
|
||||||
|
'job_id' => $job->job_id ?? null,
|
||||||
|
'account' => $accountNumber,
|
||||||
|
'period' => $period,
|
||||||
|
'client' => $clientName,
|
||||||
|
'queue_name' => $queueName
|
||||||
|
]);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Statement export jobs have been queued',
|
'success' => true,
|
||||||
'jobs' => array_map(function ($index, $jobId) use ($data) {
|
'message' => 'Statement export job queued successfully',
|
||||||
return [
|
'data' => [
|
||||||
'job_id' => $jobId,
|
'job_id' => $job->job_id ?? null,
|
||||||
'client_name' => $data[$index]['client_name'],
|
'account_number' => $accountNumber,
|
||||||
'account_number' => $data[$index]['account_number'],
|
'period' => $period,
|
||||||
'period' => $data[$index]['period'],
|
'client_name' => $clientName,
|
||||||
'file_name' => "{$data[$index]['client_name']}_{$data[$index]['account_number']}_{$data[$index]['period']}.csv"
|
'queue_name' => $queueName
|
||||||
];
|
]
|
||||||
}, array_keys($jobIds), $jobIds)
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error("Failed to export statement", [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'account' => $accountNumber,
|
||||||
|
'period' => $period,
|
||||||
|
'queue_name' => $queueName
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Failed to queue statement export job',
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'queue_name' => $queueName
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function listAccount(){
|
|
||||||
return [
|
|
||||||
'PLUANG' => [
|
|
||||||
'1080426085',
|
|
||||||
'1080425781',
|
|
||||||
],
|
|
||||||
'OY' => [
|
|
||||||
'1081647484',
|
|
||||||
'1081647485',
|
|
||||||
],
|
|
||||||
'INDORAYA' => [
|
|
||||||
'1083123710',
|
|
||||||
'1083123711',
|
|
||||||
'1083123712',
|
|
||||||
'1083123713',
|
|
||||||
'1083123714',
|
|
||||||
'1083123715',
|
|
||||||
'1083123716',
|
|
||||||
'1083123718',
|
|
||||||
'1083123719',
|
|
||||||
'1083123721',
|
|
||||||
'1083123722',
|
|
||||||
'1083123723',
|
|
||||||
'1083123724',
|
|
||||||
'1083123726',
|
|
||||||
'1083123727',
|
|
||||||
'1083123728',
|
|
||||||
'1083123730',
|
|
||||||
'1083123731',
|
|
||||||
'1083123732',
|
|
||||||
'1083123734',
|
|
||||||
'1083123735',
|
|
||||||
],
|
|
||||||
'TDC' => [
|
|
||||||
'1086677889',
|
|
||||||
'1086677890',
|
|
||||||
'1086677891',
|
|
||||||
'1086677892',
|
|
||||||
'1086677893',
|
|
||||||
'1086677894',
|
|
||||||
'1086677895',
|
|
||||||
'1086677896',
|
|
||||||
'1086677897',
|
|
||||||
],
|
|
||||||
'ASIA_PARKING' => [
|
|
||||||
'1080119298',
|
|
||||||
'1080119361',
|
|
||||||
'1080119425',
|
|
||||||
'1080119387',
|
|
||||||
'1082208069',
|
|
||||||
],
|
|
||||||
'DAU' => [
|
|
||||||
'1085151668',
|
|
||||||
],
|
|
||||||
'EGR' => [
|
|
||||||
'1085368601',
|
|
||||||
],
|
|
||||||
'SARANA_PACTINDO' => [
|
|
||||||
'1078333878',
|
|
||||||
],
|
|
||||||
'SWADAYA_PANDU' => [
|
|
||||||
'0081272689',
|
|
||||||
],
|
|
||||||
"AWAN_LINTANG_SOLUSI"=> [
|
|
||||||
"1084269430"
|
|
||||||
],
|
|
||||||
"MONETA"=> [
|
|
||||||
"1085667890"
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function listPeriod(){
|
|
||||||
return [
|
|
||||||
date('Ymd', strtotime('-1 day'))
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getAccountBalance($accountNumber, $period)
|
|
||||||
{
|
|
||||||
$accountBalance = AccountBalance::where('account_number', $accountNumber)
|
|
||||||
->where('period', '<', $period)
|
|
||||||
->orderBy('period', 'desc')
|
|
||||||
->first();
|
|
||||||
|
|
||||||
return $accountBalance->actual_balance ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function printStatementRekening($accountNumber, $period = null) {
|
|
||||||
$period = $period ?? date('Ym');
|
|
||||||
$balance = AccountBalance::where('account_number', $accountNumber)
|
|
||||||
->when($period === '202505', function($query) {
|
|
||||||
return $query->where('period', '>=', '20250512')
|
|
||||||
->orderBy('period', 'asc');
|
|
||||||
}, function($query) use ($period) {
|
|
||||||
// Get balance from last day of previous month
|
|
||||||
$firstDayOfMonth = Carbon::createFromFormat('Ym', $period)->startOfMonth();
|
|
||||||
$lastDayPrevMonth = $firstDayOfMonth->copy()->subDay()->format('Ymd');
|
|
||||||
return $query->where('period', $lastDayPrevMonth);
|
|
||||||
})
|
|
||||||
->first()
|
|
||||||
->actual_balance ?? '0.00';
|
|
||||||
$clientName = 'client1';
|
|
||||||
|
|
||||||
try {
|
|
||||||
\Log::info("Starting statement export for account: {$accountNumber}, period: {$period}, client: {$clientName}");
|
|
||||||
|
|
||||||
// Validate inputs
|
|
||||||
if (empty($accountNumber) || empty($period) || empty($clientName)) {
|
|
||||||
throw new \Exception('Required parameters missing');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch the job
|
|
||||||
$job = ExportStatementPeriodJob::dispatch($accountNumber, $period, $balance, $clientName);
|
|
||||||
|
|
||||||
\Log::info("Statement export job dispatched successfully", [
|
|
||||||
'job_id' => $job->job_id ?? null,
|
|
||||||
'account' => $accountNumber,
|
|
||||||
'period' => $period,
|
|
||||||
'client' => $clientName
|
|
||||||
]);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => true,
|
|
||||||
'message' => 'Statement export job queued successfully',
|
|
||||||
'data' => [
|
|
||||||
'job_id' => $job->job_id ?? null,
|
|
||||||
'account_number' => $accountNumber,
|
|
||||||
'period' => $period,
|
|
||||||
'client_name' => $clientName
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
\Log::error("Failed to export statement", [
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
'account' => $accountNumber,
|
|
||||||
'period' => $period
|
|
||||||
]);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Failed to queue statement export job',
|
|
||||||
'error' => $e->getMessage()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
317
app/Http/Requests/BalanceSummaryRequest.php
Normal file
317
app/Http/Requests/BalanceSummaryRequest.php
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Webstatement\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||||
|
use Modules\Webstatement\Enums\ResponseCode;
|
||||||
|
use Modules\Webstatement\Models\AccountBalance;
|
||||||
|
|
||||||
|
class BalanceSummaryRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Ambil parameter dari header
|
||||||
|
$signature = $this->header('X-Signature');
|
||||||
|
$timestamp = $this->header('X-Timestamp');
|
||||||
|
$apiKey = $this->header('X-Api-Key');
|
||||||
|
|
||||||
|
// Validasi keberadaan header yang diperlukan
|
||||||
|
if (!$signature || !$timestamp || !$apiKey) {
|
||||||
|
Log::warning('HMAC validation failed - missing required headers', [
|
||||||
|
'signature' => $signature,
|
||||||
|
'timestamp' => $timestamp,
|
||||||
|
'apiKey' => $apiKey,
|
||||||
|
'ip' => $this->ip(),
|
||||||
|
'user_agent' => $this->userAgent()
|
||||||
|
]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validasi API key dari config
|
||||||
|
$expectedApiKey = config('webstatement.api_key');
|
||||||
|
if ($apiKey !== $expectedApiKey) {
|
||||||
|
Log::warning('HMAC validation failed - invalid API key', [
|
||||||
|
'provided_api_key' => $apiKey,
|
||||||
|
'expected_api_key' => $expectedApiKey,
|
||||||
|
'ip' => $this->ip(),
|
||||||
|
'user_agent' => $this->userAgent()
|
||||||
|
]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ambil secret key dari config
|
||||||
|
$secretKey = config('webstatement.secret_key');
|
||||||
|
|
||||||
|
// Ambil parameter untuk validasi HMAC
|
||||||
|
$httpMethod = $this->method();
|
||||||
|
$relativeUrl = $this->path();
|
||||||
|
$requestBody = $this->getContent();
|
||||||
|
|
||||||
|
// Validasi HMAC signature
|
||||||
|
$isValid = validateHmac512(
|
||||||
|
$httpMethod,
|
||||||
|
$relativeUrl,
|
||||||
|
$apiKey,
|
||||||
|
$requestBody,
|
||||||
|
$timestamp,
|
||||||
|
$secretKey,
|
||||||
|
$signature
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$isValid) {
|
||||||
|
Log::warning('HMAC validation failed - invalid signature', [
|
||||||
|
'http_method' => $httpMethod,
|
||||||
|
'relative_url' => $relativeUrl,
|
||||||
|
'api_key' => $apiKey,
|
||||||
|
'timestamp' => $timestamp,
|
||||||
|
'ip' => $this->ip(),
|
||||||
|
'user_agent' => $this->userAgent()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $isValid;
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('HMAC validation error', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'ip' => $this->ip(),
|
||||||
|
'user_agent' => $this->userAgent()
|
||||||
|
]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'account_number' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'max:10',
|
||||||
|
'min:10',
|
||||||
|
'exists:account_balances,account_number',
|
||||||
|
'regex:/^[0-9]+$/' // Numeric only
|
||||||
|
],
|
||||||
|
'start_date' => [
|
||||||
|
'required',
|
||||||
|
'date_format:Y-m-d',
|
||||||
|
'before_or_equal:end_date',
|
||||||
|
'after_or_equal:1900-01-01',
|
||||||
|
'before_or_equal:today'
|
||||||
|
],
|
||||||
|
'end_date' => [
|
||||||
|
'required',
|
||||||
|
'date_format:Y-m-d',
|
||||||
|
'after_or_equal:start_date',
|
||||||
|
'after_or_equal:1900-01-01',
|
||||||
|
'before_or_equal:today'
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get custom messages for validator errors.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'account_number.exists' => 'Nomor rekening tidak ditemukan.',
|
||||||
|
'account_number.required' => 'Nomor rekening wajib diisi.',
|
||||||
|
'account_number.string' => 'Nomor rekening harus berupa teks.',
|
||||||
|
'account_number.max' => 'Nomor rekening maksimal :max karakter.',
|
||||||
|
'account_number.min' => 'Nomor rekening minimal :min karakter.',
|
||||||
|
'account_number.regex' => 'Nomor rekening hanya boleh mengandung angka.',
|
||||||
|
'start_date.required' => 'Tanggal awal wajib diisi.',
|
||||||
|
'start_date.date_format' => 'Format tanggal awal harus YYYY-MM-DD.',
|
||||||
|
'start_date.before_or_equal' => 'Tanggal awal harus sebelum atau sama dengan tanggal akhir.',
|
||||||
|
'end_date.required' => 'Tanggal akhir wajib diisi.',
|
||||||
|
'end_date.date_format' => 'Format tanggal akhir harus YYYY-MM-DD.',
|
||||||
|
'end_date.after_or_equal' => 'Tanggal akhir harus sesudah atau sama dengan tanggal awal.',
|
||||||
|
'end_date.before_or_equal' => 'Tanggal akhir harus sebelum atau sama dengan hari ini.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a failed validation attempt.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Contracts\Validation\Validator $validator
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Validation\ValidationException
|
||||||
|
*/
|
||||||
|
protected function failedValidation($validator)
|
||||||
|
{
|
||||||
|
$errors = $validator->errors();
|
||||||
|
|
||||||
|
if($errors->has('account_number') && $errors->first('account_number') === 'Nomor rekening tidak ditemukan.') {
|
||||||
|
throw new HttpResponseException(
|
||||||
|
response()->json(
|
||||||
|
ResponseCode::ACCOUNT_NOT_FOUND->toResponse(
|
||||||
|
null,
|
||||||
|
'Nomor rekening tidak ditemukan'
|
||||||
|
),
|
||||||
|
ResponseCode::ACCOUNT_NOT_FOUND->getHttpStatus()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new HttpResponseException(
|
||||||
|
response()->json(
|
||||||
|
ResponseCode::INVALID_FIELD->toResponse(
|
||||||
|
['errors' => $errors->all()],
|
||||||
|
'Validasi gagal'
|
||||||
|
),
|
||||||
|
ResponseCode::INVALID_FIELD->getHttpStatus()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle failed authorization.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
|
*/
|
||||||
|
protected function failedAuthorization()
|
||||||
|
{
|
||||||
|
$xApiKey = $this->header('X-Api-Key');
|
||||||
|
$xSignature = $this->header('X-Signature');
|
||||||
|
$xTimestamp = $this->header('X-Timestamp');
|
||||||
|
|
||||||
|
$expectedApiKey = config('webstatement.api_key');
|
||||||
|
|
||||||
|
if(!$xApiKey){
|
||||||
|
throw new HttpResponseException(
|
||||||
|
response()->json(
|
||||||
|
ResponseCode::INVALID_FIELD->toResponse(
|
||||||
|
['errors' => ['X-Api-Key' => 'API Key wajib diisi']],
|
||||||
|
'Validasi gagal'
|
||||||
|
),
|
||||||
|
ResponseCode::INVALID_FIELD->getHttpStatus()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$xSignature){
|
||||||
|
throw new HttpResponseException(
|
||||||
|
response()->json(
|
||||||
|
ResponseCode::INVALID_FIELD->toResponse(
|
||||||
|
['errors' => ['X-Signature' => 'Signature wajib diisi']],
|
||||||
|
'Validasi gagal'
|
||||||
|
),
|
||||||
|
ResponseCode::INVALID_FIELD->getHttpStatus()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$xTimestamp){
|
||||||
|
throw new HttpResponseException(
|
||||||
|
response()->json(
|
||||||
|
ResponseCode::INVALID_FIELD->toResponse(
|
||||||
|
['errors' => ['X-Timestamp' => 'Timestamp wajib diisi']],
|
||||||
|
'Validasi gagal'
|
||||||
|
),
|
||||||
|
ResponseCode::INVALID_FIELD->getHttpStatus()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validasi format timestamp ISO 8601
|
||||||
|
if (!preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/', $xTimestamp)) {
|
||||||
|
throw new HttpResponseException(
|
||||||
|
response()->json(
|
||||||
|
ResponseCode::INVALID_FIELD->toResponse(
|
||||||
|
['errors' => ['X-Timestamp' => 'Format timestamp tidak valid. Gunakan format ISO 8601 (YYYY-MM-DDTHH:MM:SS.sssZ)']],
|
||||||
|
'Validasi gagal'
|
||||||
|
),
|
||||||
|
ResponseCode::INVALID_FIELD->getHttpStatus()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validasi timestamp tidak lebih dari 5 menit dari waktu sekarang
|
||||||
|
try {
|
||||||
|
$timestamp = new \DateTime($xTimestamp);
|
||||||
|
$now = new \DateTime();
|
||||||
|
$diff = $now->getTimestamp() - $timestamp->getTimestamp();
|
||||||
|
|
||||||
|
if (abs($diff) > 300) { // 5 menit = 300 detik
|
||||||
|
throw new HttpResponseException(
|
||||||
|
response()->json(
|
||||||
|
ResponseCode::INVALID_FIELD->toResponse(
|
||||||
|
['errors' => ['X-Timestamp' => 'Timestamp expired. Maksimal selisih 5 menit dari waktu sekarang']],
|
||||||
|
'Validasi gagal'
|
||||||
|
),
|
||||||
|
ResponseCode::INVALID_FIELD->getHttpStatus()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new HttpResponseException(
|
||||||
|
response()->json(
|
||||||
|
ResponseCode::INVALID_FIELD->toResponse(
|
||||||
|
['errors' => ['X-Timestamp' => 'Timestamp tidak dapat diproses']],
|
||||||
|
'Validasi gagal'
|
||||||
|
),
|
||||||
|
ResponseCode::INVALID_FIELD->getHttpStatus()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cek apakah ini karena invalid API key
|
||||||
|
if ($xApiKey !== $expectedApiKey) {
|
||||||
|
throw new HttpResponseException(
|
||||||
|
response()->json(
|
||||||
|
ResponseCode::INVALID_TOKEN->toResponse(
|
||||||
|
null,
|
||||||
|
'API Key tidak valid'
|
||||||
|
),
|
||||||
|
ResponseCode::INVALID_TOKEN->getHttpStatus()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Untuk kasus HMAC signature tidak valid
|
||||||
|
throw new HttpResponseException(
|
||||||
|
response()->json(
|
||||||
|
ResponseCode::UNAUTHORIZED->toResponse(
|
||||||
|
null,
|
||||||
|
'Signature tidak valid'
|
||||||
|
),
|
||||||
|
ResponseCode::UNAUTHORIZED->getHttpStatus()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the data for validation.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function prepareForValidation(): void
|
||||||
|
{
|
||||||
|
Log::info('Balance summary request received', [
|
||||||
|
'input' => $this->all(),
|
||||||
|
'ip' => $this->ip(),
|
||||||
|
'user_agent' => $this->userAgent()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
app/Http/Resources/BalanceSummaryResource.php
Normal file
44
app/Http/Resources/BalanceSummaryResource.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Webstatement\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class BalanceSummaryResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function toArray($request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'account_number' => $this['account_number'],
|
||||||
|
'period' => [
|
||||||
|
'start_date' => $this['period']['start_date'],
|
||||||
|
'end_date' => $this['period']['end_date'],
|
||||||
|
],
|
||||||
|
'opening_balance' => [
|
||||||
|
'date' => $this['opening_balance']['date'],
|
||||||
|
'balance' => $this['opening_balance']['balance'],
|
||||||
|
'formatted_balance' => number_format($this['opening_balance']['balance'], 2, ',', '.'),
|
||||||
|
],
|
||||||
|
'closing_balance' => [
|
||||||
|
'date' => $this['closing_balance']['date'],
|
||||||
|
'balance' => $this['closing_balance']['balance'],
|
||||||
|
'formatted_balance' => number_format($this['closing_balance']['balance'], 2, ',', '.'),
|
||||||
|
'base_balance' => [
|
||||||
|
'date' => $this['closing_balance']['base_balance']['date'],
|
||||||
|
'balance' => $this['closing_balance']['base_balance']['balance'],
|
||||||
|
'formatted_balance' => number_format($this['closing_balance']['base_balance']['balance'], 2, ',', '.'),
|
||||||
|
],
|
||||||
|
'transactions_on_end_date' => $this['closing_balance']['transactions_on_end_date'],
|
||||||
|
'formatted_transactions_on_end_date' => number_format($this['closing_balance']['transactions_on_end_date'], 2, ',', '.'),
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@ class ProcessAccountDataJob implements ShouldQueue
|
|||||||
private const CSV_DELIMITER = '~';
|
private const CSV_DELIMITER = '~';
|
||||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||||
private const FILENAME = 'ST.ACCOUNT.csv';
|
private const FILENAME = 'ST.ACCOUNT.csv';
|
||||||
private const DISK_NAME = 'sftpStatement';
|
private const DISK_NAME = 'staging';
|
||||||
private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage
|
private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage
|
||||||
|
|
||||||
private string $period = '';
|
private string $period = '';
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
private const CSV_DELIMITER = '~';
|
private const CSV_DELIMITER = '~';
|
||||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||||
private const FILENAME = 'ST.AA.ARRANGEMENT.csv';
|
private const FILENAME = 'ST.AA.ARRANGEMENT.csv';
|
||||||
private const DISK_NAME = 'sftpStatement';
|
private const DISK_NAME = 'staging';
|
||||||
private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage
|
private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage
|
||||||
|
|
||||||
private string $period = '';
|
private string $period = '';
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
private const CSV_DELIMITER = '~';
|
private const CSV_DELIMITER = '~';
|
||||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||||
private const FILENAME = 'ST.ATM.TRANSACTION.csv';
|
private const FILENAME = 'ST.ATM.TRANSACTION.csv';
|
||||||
private const DISK_NAME = 'sftpStatement';
|
private const DISK_NAME = 'staging';
|
||||||
private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage
|
private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage
|
||||||
private const HEADER_MAP = [
|
private const HEADER_MAP = [
|
||||||
'id' => 'transaction_id',
|
'id' => 'transaction_id',
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
private const CSV_DELIMITER = '~';
|
private const CSV_DELIMITER = '~';
|
||||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||||
private const FILENAME = 'ST.AA.BILL.DETAILS.csv';
|
private const FILENAME = 'ST.AA.BILL.DETAILS.csv';
|
||||||
private const DISK_NAME = 'sftpStatement';
|
private const DISK_NAME = 'staging';
|
||||||
private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage
|
private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage
|
||||||
|
|
||||||
private string $period = '';
|
private string $period = '';
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
private const CSV_DELIMITER = '~';
|
private const CSV_DELIMITER = '~';
|
||||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||||
private const FILENAME = 'ST.CATEGORY.csv';
|
private const FILENAME = 'ST.CATEGORY.csv';
|
||||||
private const DISK_NAME = 'sftpStatement';
|
private const DISK_NAME = 'staging';
|
||||||
private const HEADER_MAP = [
|
private const HEADER_MAP = [
|
||||||
'id' => 'id_category',
|
'id' => 'id_category',
|
||||||
'date_time' => 'date_time',
|
'date_time' => 'date_time',
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
private const CSV_DELIMITER = '~';
|
private const CSV_DELIMITER = '~';
|
||||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||||
private const FILENAME = 'ST.COMPANY.csv';
|
private const FILENAME = 'ST.COMPANY.csv';
|
||||||
private const DISK_NAME = 'sftpStatement';
|
private const DISK_NAME = 'staging';
|
||||||
private const FIELD_MAP = [
|
private const FIELD_MAP = [
|
||||||
'id' => null, // Not mapped to model
|
'id' => null, // Not mapped to model
|
||||||
'date_time' => null, // Not mapped to model
|
'date_time' => null, // Not mapped to model
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Modules\Webstatement\Models\Customer;
|
use Modules\Webstatement\Models\Customer;
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
private const CSV_DELIMITER = '~';
|
private const CSV_DELIMITER = '~';
|
||||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||||
private const FILENAME = 'ST.CUSTOMER.csv';
|
private const FILENAME = 'ST.CUSTOMER.csv';
|
||||||
private const DISK_NAME = 'sftpStatement';
|
private const DISK_NAME = 'staging';
|
||||||
private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage
|
private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage
|
||||||
|
|
||||||
private string $period = '';
|
private string $period = '';
|
||||||
@@ -112,13 +113,28 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$headers = (new Customer())->getFillable();
|
// Read header from CSV file
|
||||||
|
$csvHeaders = fgetcsv($handle, 0, self::CSV_DELIMITER);
|
||||||
|
if ($csvHeaders === false) {
|
||||||
|
Log::error("Unable to read headers from file: $filePath");
|
||||||
|
fclose($handle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map CSV headers to database fields
|
||||||
|
$headerMapping = $this->getHeaderMapping($csvHeaders);
|
||||||
|
|
||||||
|
Log::info("CSV Headers found", [
|
||||||
|
'csv_headers' => $csvHeaders,
|
||||||
|
'mapped_fields' => array_values($headerMapping)
|
||||||
|
]);
|
||||||
|
|
||||||
$rowCount = 0;
|
$rowCount = 0;
|
||||||
$chunkCount = 0;
|
$chunkCount = 0;
|
||||||
|
|
||||||
while (($row = fgetcsv($handle, 0, self::CSV_DELIMITER)) !== false) {
|
while (($row = fgetcsv($handle, 0, self::CSV_DELIMITER)) !== false) {
|
||||||
$rowCount++;
|
$rowCount++;
|
||||||
$this->processRow($row, $headers, $rowCount, $filePath);
|
$this->processRow($row, $csvHeaders, $headerMapping, $rowCount, $filePath);
|
||||||
|
|
||||||
// Process in chunks to avoid memory issues
|
// Process in chunks to avoid memory issues
|
||||||
if (count($this->customerBatch) >= self::CHUNK_SIZE) {
|
if (count($this->customerBatch) >= self::CHUNK_SIZE) {
|
||||||
@@ -137,16 +153,61 @@
|
|||||||
Log::info("Completed processing $filePath. Processed {$this->processedCount} records with {$this->errorCount} errors.");
|
Log::info("Completed processing $filePath. Processed {$this->processedCount} records with {$this->errorCount} errors.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private function processRow(array $row, array $headers, int $rowCount, string $filePath)
|
/**
|
||||||
|
* Map CSV headers to database field names
|
||||||
|
* Memetakan header CSV ke nama field database
|
||||||
|
*/
|
||||||
|
private function getHeaderMapping(array $csvHeaders): array
|
||||||
|
{
|
||||||
|
$mapping = [];
|
||||||
|
$fillableFields = (new Customer())->getFillable();
|
||||||
|
|
||||||
|
foreach ($csvHeaders as $index => $csvHeader) {
|
||||||
|
$csvHeader = trim($csvHeader);
|
||||||
|
|
||||||
|
// Direct mapping untuk field yang sama
|
||||||
|
if (in_array($csvHeader, $fillableFields)) {
|
||||||
|
$mapping[$index] = $csvHeader;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom mapping untuk field yang berbeda nama
|
||||||
|
$customMapping = [
|
||||||
|
'co_code' => 'branch_code', // co_code di CSV menjadi branch_code di database
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isset($customMapping[$csvHeader])) {
|
||||||
|
$mapping[$index] = $customMapping[$csvHeader];
|
||||||
|
} else {
|
||||||
|
// Jika field ada di fillable, gunakan langsung
|
||||||
|
if (in_array($csvHeader, $fillableFields)) {
|
||||||
|
$mapping[$index] = $csvHeader;
|
||||||
|
}
|
||||||
|
// Jika tidak ada mapping, skip field ini
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function processRow(array $row, array $csvHeaders, array $headerMapping, int $rowCount, string $filePath)
|
||||||
: void
|
: void
|
||||||
{
|
{
|
||||||
if (count($headers) !== count($row)) {
|
if (count($csvHeaders) !== count($row)) {
|
||||||
Log::warning("Row $rowCount in $filePath has incorrect column count. Expected: " .
|
Log::warning("Row $rowCount in $filePath has incorrect column count. Expected: " .
|
||||||
count($headers) . ", Got: " . count($row));
|
count($csvHeaders) . ", Got: " . count($row));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = array_combine($headers, $row);
|
// Map CSV data to database fields
|
||||||
|
$data = [];
|
||||||
|
foreach ($row as $index => $value) {
|
||||||
|
if (isset($headerMapping[$index])) {
|
||||||
|
$fieldName = $headerMapping[$index];
|
||||||
|
$data[$fieldName] = trim($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->addToBatch($data, $rowCount, $filePath);
|
$this->addToBatch($data, $rowCount, $filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,12 +236,20 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Save batched records to the database
|
* Save batched records to the database
|
||||||
|
* Menyimpan data customer dalam batch ke database dengan transaksi
|
||||||
*/
|
*/
|
||||||
private function saveBatch()
|
private function saveBatch()
|
||||||
: void
|
: void
|
||||||
{
|
{
|
||||||
|
if (empty($this->customerBatch)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$batchSize = count($this->customerBatch);
|
||||||
|
Log::info("Starting batch save", ['batch_size' => $batchSize]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!empty($this->customerBatch)) {
|
DB::transaction(function () use ($batchSize) {
|
||||||
// Bulk insert/update customers
|
// Bulk insert/update customers
|
||||||
Customer::upsert(
|
Customer::upsert(
|
||||||
$this->customerBatch,
|
$this->customerBatch,
|
||||||
@@ -188,14 +257,26 @@
|
|||||||
array_diff((new Customer())->getFillable(), ['customer_code']) // Update columns
|
array_diff((new Customer())->getFillable(), ['customer_code']) // Update columns
|
||||||
);
|
);
|
||||||
|
|
||||||
// Reset customer batch after processing
|
Log::info("Batch save completed successfully", ['batch_size' => $batchSize]);
|
||||||
$this->customerBatch = [];
|
});
|
||||||
}
|
|
||||||
|
// Reset customer batch after successful processing
|
||||||
|
$this->customerBatch = [];
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
Log::error("Error in saveBatch: " . $e->getMessage());
|
Log::error("Error in saveBatch", [
|
||||||
$this->errorCount += count($this->customerBatch);
|
'error' => $e->getMessage(),
|
||||||
|
'batch_size' => $batchSize,
|
||||||
|
'trace' => $e->getTraceAsString()
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->errorCount += $batchSize;
|
||||||
|
|
||||||
// Reset batch even if there's an error to prevent reprocessing the same failed records
|
// Reset batch even if there's an error to prevent reprocessing the same failed records
|
||||||
$this->customerBatch = [];
|
$this->customerBatch = [];
|
||||||
|
|
||||||
|
// Re-throw exception untuk handling di level atas
|
||||||
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
private const CSV_DELIMITER = '~';
|
private const CSV_DELIMITER = '~';
|
||||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||||
private const FILENAME = 'ST.DATA.CAPTURE.csv';
|
private const FILENAME = 'ST.DATA.CAPTURE.csv';
|
||||||
private const DISK_NAME = 'sftpStatement';
|
private const DISK_NAME = 'staging';
|
||||||
private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage
|
private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage
|
||||||
private const CSV_HEADERS = [
|
private const CSV_HEADERS = [
|
||||||
'id',
|
'id',
|
||||||
@@ -187,7 +187,7 @@
|
|||||||
{
|
{
|
||||||
// Exclude the last field from CSV
|
// Exclude the last field from CSV
|
||||||
if (count($row) > 0) {
|
if (count($row) > 0) {
|
||||||
array_pop($row);
|
//array_pop($row);
|
||||||
Log::info("Excluded last field from row $rowCount. New column count: " . count($row));
|
Log::info("Excluded last field from row $rowCount. New column count: " . count($row));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
];
|
];
|
||||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||||
private const FILENAME = 'ST.FT.TXN.TYPE.CONDITION.csv';
|
private const FILENAME = 'ST.FT.TXN.TYPE.CONDITION.csv';
|
||||||
private const DISK_NAME = 'sftpStatement';
|
private const DISK_NAME = 'staging';
|
||||||
|
|
||||||
private string $period = '';
|
private string $period = '';
|
||||||
private int $processedCount = 0;
|
private int $processedCount = 0;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
private const CSV_DELIMITER = '~';
|
private const CSV_DELIMITER = '~';
|
||||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||||
private const FILENAME = 'ST.FUNDS.TRANSFER.csv';
|
private const FILENAME = 'ST.FUNDS.TRANSFER.csv';
|
||||||
private const DISK_NAME = 'sftpStatement';
|
private const DISK_NAME = 'staging';
|
||||||
|
|
||||||
private string $period = '';
|
private string $period = '';
|
||||||
private int $processedCount = 0;
|
private int $processedCount = 0;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class ProcessProvinceDataJob implements ShouldQueue
|
|||||||
private const CSV_DELIMITER = '~';
|
private const CSV_DELIMITER = '~';
|
||||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||||
private const FILENAME = 'ST.PROVINCE.csv';
|
private const FILENAME = 'ST.PROVINCE.csv';
|
||||||
private const DISK_NAME = 'sftpStatement';
|
private const DISK_NAME = 'staging';
|
||||||
|
|
||||||
private string $period = '';
|
private string $period = '';
|
||||||
private int $processedCount = 0;
|
private int $processedCount = 0;
|
||||||
@@ -29,7 +29,7 @@ class ProcessProvinceDataJob implements ShouldQueue
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Membuat instance job baru untuk memproses data provinsi
|
* Membuat instance job baru untuk memproses data provinsi
|
||||||
*
|
*
|
||||||
* @param string $period Periode data yang akan diproses
|
* @param string $period Periode data yang akan diproses
|
||||||
*/
|
*/
|
||||||
public function __construct(string $period = '')
|
public function __construct(string $period = '')
|
||||||
@@ -41,17 +41,17 @@ class ProcessProvinceDataJob implements ShouldQueue
|
|||||||
/**
|
/**
|
||||||
* Menjalankan job untuk memproses file ST.PROVINCE.csv
|
* Menjalankan job untuk memproses file ST.PROVINCE.csv
|
||||||
* Menggunakan transaction untuk memastikan konsistensi data
|
* Menggunakan transaction untuk memastikan konsistensi data
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
DB::beginTransaction();
|
DB::beginTransaction();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Log::info('ProcessProvinceDataJob: Memulai pemrosesan data provinsi');
|
Log::info('ProcessProvinceDataJob: Memulai pemrosesan data provinsi');
|
||||||
|
|
||||||
$this->initializeJob();
|
$this->initializeJob();
|
||||||
|
|
||||||
if ($this->period === '') {
|
if ($this->period === '') {
|
||||||
@@ -62,10 +62,10 @@ class ProcessProvinceDataJob implements ShouldQueue
|
|||||||
|
|
||||||
$this->processPeriod();
|
$this->processPeriod();
|
||||||
$this->logJobCompletion();
|
$this->logJobCompletion();
|
||||||
|
|
||||||
DB::commit();
|
DB::commit();
|
||||||
Log::info('ProcessProvinceDataJob: Transaction berhasil di-commit');
|
Log::info('ProcessProvinceDataJob: Transaction berhasil di-commit');
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
DB::rollback();
|
DB::rollback();
|
||||||
Log::error('ProcessProvinceDataJob: Error dalam pemrosesan, transaction di-rollback: ' . $e->getMessage());
|
Log::error('ProcessProvinceDataJob: Error dalam pemrosesan, transaction di-rollback: ' . $e->getMessage());
|
||||||
@@ -76,7 +76,7 @@ class ProcessProvinceDataJob implements ShouldQueue
|
|||||||
/**
|
/**
|
||||||
* Inisialisasi pengaturan job
|
* Inisialisasi pengaturan job
|
||||||
* Mengatur timeout dan reset counter
|
* Mengatur timeout dan reset counter
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
private function initializeJob(): void
|
private function initializeJob(): void
|
||||||
@@ -85,14 +85,14 @@ class ProcessProvinceDataJob implements ShouldQueue
|
|||||||
$this->processedCount = 0;
|
$this->processedCount = 0;
|
||||||
$this->errorCount = 0;
|
$this->errorCount = 0;
|
||||||
$this->skippedCount = 0;
|
$this->skippedCount = 0;
|
||||||
|
|
||||||
Log::info('ProcessProvinceDataJob: Job diinisialisasi dengan timeout ' . self::MAX_EXECUTION_TIME . ' detik');
|
Log::info('ProcessProvinceDataJob: Job diinisialisasi dengan timeout ' . self::MAX_EXECUTION_TIME . ' detik');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Memproses file untuk periode tertentu
|
* Memproses file untuk periode tertentu
|
||||||
* Mengambil file dari SFTP dan memproses data
|
* Mengambil file dari SFTP dan memproses data
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
private function processPeriod(): void
|
private function processPeriod(): void
|
||||||
@@ -101,7 +101,7 @@ class ProcessProvinceDataJob implements ShouldQueue
|
|||||||
$filePath = "$this->period/" . self::FILENAME;
|
$filePath = "$this->period/" . self::FILENAME;
|
||||||
|
|
||||||
Log::info('ProcessProvinceDataJob: Memproses periode ' . $this->period);
|
Log::info('ProcessProvinceDataJob: Memproses periode ' . $this->period);
|
||||||
|
|
||||||
if (!$this->validateFile($disk, $filePath)) {
|
if (!$this->validateFile($disk, $filePath)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ class ProcessProvinceDataJob implements ShouldQueue
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validasi keberadaan file di storage
|
* Validasi keberadaan file di storage
|
||||||
*
|
*
|
||||||
* @param mixed $disk Storage disk instance
|
* @param mixed $disk Storage disk instance
|
||||||
* @param string $filePath Path file yang akan divalidasi
|
* @param string $filePath Path file yang akan divalidasi
|
||||||
* @return bool
|
* @return bool
|
||||||
@@ -133,7 +133,7 @@ class ProcessProvinceDataJob implements ShouldQueue
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Membuat file temporary untuk pemrosesan
|
* Membuat file temporary untuk pemrosesan
|
||||||
*
|
*
|
||||||
* @param mixed $disk Storage disk instance
|
* @param mixed $disk Storage disk instance
|
||||||
* @param string $filePath Path file sumber
|
* @param string $filePath Path file sumber
|
||||||
* @return string Path file temporary
|
* @return string Path file temporary
|
||||||
@@ -142,7 +142,7 @@ class ProcessProvinceDataJob implements ShouldQueue
|
|||||||
{
|
{
|
||||||
$tempFilePath = storage_path("app/temp_" . self::FILENAME);
|
$tempFilePath = storage_path("app/temp_" . self::FILENAME);
|
||||||
file_put_contents($tempFilePath, $disk->get($filePath));
|
file_put_contents($tempFilePath, $disk->get($filePath));
|
||||||
|
|
||||||
Log::info("ProcessProvinceDataJob: File temporary dibuat: $tempFilePath");
|
Log::info("ProcessProvinceDataJob: File temporary dibuat: $tempFilePath");
|
||||||
return $tempFilePath;
|
return $tempFilePath;
|
||||||
}
|
}
|
||||||
@@ -150,7 +150,7 @@ class ProcessProvinceDataJob implements ShouldQueue
|
|||||||
/**
|
/**
|
||||||
* Memproses file CSV dan mengimpor data ke database
|
* Memproses file CSV dan mengimpor data ke database
|
||||||
* Format CSV: id~date_time~province~province_name
|
* Format CSV: id~date_time~province~province_name
|
||||||
*
|
*
|
||||||
* @param string $tempFilePath Path file temporary
|
* @param string $tempFilePath Path file temporary
|
||||||
* @param string $filePath Path file asli untuk logging
|
* @param string $filePath Path file asli untuk logging
|
||||||
* @return void
|
* @return void
|
||||||
@@ -164,20 +164,20 @@ class ProcessProvinceDataJob implements ShouldQueue
|
|||||||
}
|
}
|
||||||
|
|
||||||
Log::info("ProcessProvinceDataJob: Memulai pemrosesan file: $filePath");
|
Log::info("ProcessProvinceDataJob: Memulai pemrosesan file: $filePath");
|
||||||
|
|
||||||
$rowCount = 0;
|
$rowCount = 0;
|
||||||
$isFirstRow = true;
|
$isFirstRow = true;
|
||||||
|
|
||||||
while (($row = fgetcsv($handle, 0, self::CSV_DELIMITER)) !== false) {
|
while (($row = fgetcsv($handle, 0, self::CSV_DELIMITER)) !== false) {
|
||||||
$rowCount++;
|
$rowCount++;
|
||||||
|
|
||||||
// Skip header row
|
// Skip header row
|
||||||
if ($isFirstRow) {
|
if ($isFirstRow) {
|
||||||
$isFirstRow = false;
|
$isFirstRow = false;
|
||||||
Log::info("ProcessProvinceDataJob: Melewati header row: " . implode(self::CSV_DELIMITER, $row));
|
Log::info("ProcessProvinceDataJob: Melewati header row: " . implode(self::CSV_DELIMITER, $row));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->processRow($row, $rowCount, $filePath);
|
$this->processRow($row, $rowCount, $filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ class ProcessProvinceDataJob implements ShouldQueue
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Memproses satu baris data CSV
|
* Memproses satu baris data CSV
|
||||||
*
|
*
|
||||||
* @param array $row Data baris CSV
|
* @param array $row Data baris CSV
|
||||||
* @param int $rowCount Nomor baris untuk logging
|
* @param int $rowCount Nomor baris untuk logging
|
||||||
* @param string $filePath Path file untuk logging
|
* @param string $filePath Path file untuk logging
|
||||||
@@ -207,16 +207,16 @@ class ProcessProvinceDataJob implements ShouldQueue
|
|||||||
'code' => trim($row[2]), // province code
|
'code' => trim($row[2]), // province code
|
||||||
'name' => trim($row[3]) // province_name
|
'name' => trim($row[3]) // province_name
|
||||||
];
|
];
|
||||||
|
|
||||||
Log::debug("ProcessProvinceDataJob: Memproses baris $rowCount dengan data: " . json_encode($data));
|
Log::debug("ProcessProvinceDataJob: Memproses baris $rowCount dengan data: " . json_encode($data));
|
||||||
|
|
||||||
$this->saveRecord($data, $rowCount, $filePath);
|
$this->saveRecord($data, $rowCount, $filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Menyimpan record provinsi ke database
|
* Menyimpan record provinsi ke database
|
||||||
* Menggunakan updateOrCreate untuk menghindari duplikasi
|
* Menggunakan updateOrCreate untuk menghindari duplikasi
|
||||||
*
|
*
|
||||||
* @param array $data Data provinsi yang akan disimpan
|
* @param array $data Data provinsi yang akan disimpan
|
||||||
* @param int $rowCount Nomor baris untuk logging
|
* @param int $rowCount Nomor baris untuk logging
|
||||||
* @param string $filePath Path file untuk logging
|
* @param string $filePath Path file untuk logging
|
||||||
@@ -237,10 +237,10 @@ class ProcessProvinceDataJob implements ShouldQueue
|
|||||||
['code' => $data['code']], // Kondisi pencarian
|
['code' => $data['code']], // Kondisi pencarian
|
||||||
['name' => $data['name']] // Data yang akan diupdate/insert
|
['name' => $data['name']] // Data yang akan diupdate/insert
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->processedCount++;
|
$this->processedCount++;
|
||||||
Log::debug("ProcessProvinceDataJob: Berhasil menyimpan provinsi ID: {$province->id}, Code: {$data['code']}, Name: {$data['name']}");
|
Log::debug("ProcessProvinceDataJob: Berhasil menyimpan provinsi ID: {$province->id}, Code: {$data['code']}, Name: {$data['name']}");
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$this->errorCount++;
|
$this->errorCount++;
|
||||||
Log::error("ProcessProvinceDataJob: Error menyimpan data provinsi pada baris $rowCount di $filePath: " . $e->getMessage());
|
Log::error("ProcessProvinceDataJob: Error menyimpan data provinsi pada baris $rowCount di $filePath: " . $e->getMessage());
|
||||||
@@ -250,7 +250,7 @@ class ProcessProvinceDataJob implements ShouldQueue
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Membersihkan file temporary
|
* Membersihkan file temporary
|
||||||
*
|
*
|
||||||
* @param string $tempFilePath Path file temporary yang akan dihapus
|
* @param string $tempFilePath Path file temporary yang akan dihapus
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
@@ -264,7 +264,7 @@ class ProcessProvinceDataJob implements ShouldQueue
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Logging hasil akhir pemrosesan job
|
* Logging hasil akhir pemrosesan job
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
private function logJobCompletion(): void
|
private function logJobCompletion(): void
|
||||||
@@ -273,14 +273,14 @@ class ProcessProvinceDataJob implements ShouldQueue
|
|||||||
"Total diproses: {$this->processedCount}, " .
|
"Total diproses: {$this->processedCount}, " .
|
||||||
"Total error: {$this->errorCount}, " .
|
"Total error: {$this->errorCount}, " .
|
||||||
"Total dilewati: {$this->skippedCount}";
|
"Total dilewati: {$this->skippedCount}";
|
||||||
|
|
||||||
Log::info($message);
|
Log::info($message);
|
||||||
|
|
||||||
// Log summary untuk monitoring
|
// Log summary untuk monitoring
|
||||||
if ($this->errorCount > 0) {
|
if ($this->errorCount > 0) {
|
||||||
Log::warning("ProcessProvinceDataJob: Terdapat {$this->errorCount} error dalam pemrosesan");
|
Log::warning("ProcessProvinceDataJob: Terdapat {$this->errorCount} error dalam pemrosesan");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->skippedCount > 0) {
|
if ($this->skippedCount > 0) {
|
||||||
Log::info("ProcessProvinceDataJob: Terdapat {$this->skippedCount} baris yang dilewati");
|
Log::info("ProcessProvinceDataJob: Terdapat {$this->skippedCount} baris yang dilewati");
|
||||||
}
|
}
|
||||||
@@ -288,7 +288,7 @@ class ProcessProvinceDataJob implements ShouldQueue
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle job failure
|
* Handle job failure
|
||||||
*
|
*
|
||||||
* @param Exception $exception
|
* @param Exception $exception
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
@@ -297,4 +297,4 @@ class ProcessProvinceDataJob implements ShouldQueue
|
|||||||
Log::error('ProcessProvinceDataJob: Job gagal dijalankan: ' . $exception->getMessage());
|
Log::error('ProcessProvinceDataJob: Job gagal dijalankan: ' . $exception->getMessage());
|
||||||
Log::error('ProcessProvinceDataJob: Stack trace: ' . $exception->getTraceAsString());
|
Log::error('ProcessProvinceDataJob: Stack trace: ' . $exception->getTraceAsString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class ProcessSectorDataJob implements ShouldQueue
|
|||||||
private const CSV_DELIMITER = '~';
|
private const CSV_DELIMITER = '~';
|
||||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||||
private const FILENAME = 'ST.SECTOR.csv';
|
private const FILENAME = 'ST.SECTOR.csv';
|
||||||
private const DISK_NAME = 'sftpStatement';
|
private const DISK_NAME = 'staging';
|
||||||
|
|
||||||
private string $period = '';
|
private string $period = '';
|
||||||
private int $processedCount = 0;
|
private int $processedCount = 0;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
private const CSV_DELIMITER = '~';
|
private const CSV_DELIMITER = '~';
|
||||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||||
private const FILENAME = 'ST.STMT.ENTRY.csv';
|
private const FILENAME = 'ST.STMT.ENTRY.csv';
|
||||||
private const DISK_NAME = 'sftpStatement';
|
private const DISK_NAME = 'staging';
|
||||||
private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage
|
private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage
|
||||||
|
|
||||||
private string $period = '';
|
private string $period = '';
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class ProcessStmtEntryDetailDataJob implements ShouldQueue
|
|||||||
private const CSV_DELIMITER = '~';
|
private const CSV_DELIMITER = '~';
|
||||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||||
private const FILENAME = 'ST.STMT.ENTRY.DETAIL.csv';
|
private const FILENAME = 'ST.STMT.ENTRY.DETAIL.csv';
|
||||||
private const DISK_NAME = 'sftpStatement';
|
private const DISK_NAME = 'staging';
|
||||||
private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage
|
private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage
|
||||||
|
|
||||||
private string $period = '';
|
private string $period = '';
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
private const CSV_DELIMITER = '~';
|
private const CSV_DELIMITER = '~';
|
||||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||||
private const FILENAME = 'ST.STMT.NARR.FORMAT.csv';
|
private const FILENAME = 'ST.STMT.NARR.FORMAT.csv';
|
||||||
private const DISK_NAME = 'sftpStatement';
|
private const DISK_NAME = 'staging';
|
||||||
|
|
||||||
private string $period = '';
|
private string $period = '';
|
||||||
private int $processedCount = 0;
|
private int $processedCount = 0;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
private const CSV_DELIMITER = '~';
|
private const CSV_DELIMITER = '~';
|
||||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||||
private const FILENAME = 'ST.STMT.NARR.PARAM.csv';
|
private const FILENAME = 'ST.STMT.NARR.PARAM.csv';
|
||||||
private const DISK_NAME = 'sftpStatement';
|
private const DISK_NAME = 'staging';
|
||||||
|
|
||||||
private string $period = '';
|
private string $period = '';
|
||||||
private int $processedCount = 0;
|
private int $processedCount = 0;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
private const CSV_DELIMITER = '~';
|
private const CSV_DELIMITER = '~';
|
||||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||||
private const FILENAME = 'ST.TELLER.csv';
|
private const FILENAME = 'ST.TELLER.csv';
|
||||||
private const DISK_NAME = 'sftpStatement';
|
private const DISK_NAME = 'staging';
|
||||||
private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage
|
private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage
|
||||||
private const HEADER_MAP = [
|
private const HEADER_MAP = [
|
||||||
'id' => 'id_teller',
|
'id' => 'id_teller',
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
private const CSV_DELIMITER = '~';
|
private const CSV_DELIMITER = '~';
|
||||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||||
private const FILENAME = 'ST.TRANSACTION.csv';
|
private const FILENAME = 'ST.TRANSACTION.csv';
|
||||||
private const DISK_NAME = 'sftpStatement';
|
private const DISK_NAME = 'staging';
|
||||||
|
|
||||||
private string $period = '';
|
private string $period = '';
|
||||||
private int $processedCount = 0;
|
private int $processedCount = 0;
|
||||||
|
|||||||
@@ -29,8 +29,33 @@ class Customer extends Model
|
|||||||
'home_rw',
|
'home_rw',
|
||||||
'ktp_rt',
|
'ktp_rt',
|
||||||
'ktp_rw',
|
'ktp_rw',
|
||||||
'local_ref'
|
'local_ref',
|
||||||
|
'ktp_kelurahan',
|
||||||
|
'ktp_kecamatan',
|
||||||
|
'town_country',
|
||||||
|
'ktp_provinsi',
|
||||||
|
'post_code',
|
||||||
|
'l_dom_street',
|
||||||
|
'l_dom_rt',
|
||||||
|
'l_dom_kelurahan',
|
||||||
|
'l_dom_rw',
|
||||||
|
'l_dom_kecamatan',
|
||||||
|
'l_dom_provinsi',
|
||||||
|
'l_dom_t_country',
|
||||||
|
'l_dom_post_code'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attributes that should be cast.
|
||||||
|
* Mendefinisikan casting untuk field-field tertentu
|
||||||
|
*/
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'date_of_birth' => 'date',
|
||||||
|
'birth_incorp_date' => 'date',
|
||||||
|
];
|
||||||
|
}
|
||||||
public function accounts(){
|
public function accounts(){
|
||||||
return $this->hasMany(Account::class, 'customer_code', 'customer_code');
|
return $this->hasMany(Account::class, 'customer_code', 'customer_code');
|
||||||
}
|
}
|
||||||
|
|||||||
48
app/Models/ProcessedClosingBalance.php
Normal file
48
app/Models/ProcessedClosingBalance.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Webstatement\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ProcessedClosingBalance extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'account_number',
|
||||||
|
'period',
|
||||||
|
'group_name',
|
||||||
|
'sequence_no',
|
||||||
|
'trans_reference',
|
||||||
|
'booking_date',
|
||||||
|
'transaction_date',
|
||||||
|
'amount_lcy',
|
||||||
|
'debit_acct_no',
|
||||||
|
'debit_value_date',
|
||||||
|
'debit_amount',
|
||||||
|
'credit_acct_no',
|
||||||
|
'bif_rcv_acct',
|
||||||
|
'bif_rcv_name',
|
||||||
|
'credit_value_date',
|
||||||
|
'credit_amount',
|
||||||
|
'at_unique_id',
|
||||||
|
'bif_ref_no',
|
||||||
|
'atm_order_id',
|
||||||
|
'recipt_no',
|
||||||
|
'api_iss_acct',
|
||||||
|
'api_benff_acct',
|
||||||
|
'authoriser',
|
||||||
|
'remarks',
|
||||||
|
'payment_details',
|
||||||
|
'ref_no',
|
||||||
|
'merchant_id',
|
||||||
|
'term_id',
|
||||||
|
'closing_balance',
|
||||||
|
'unique_hash',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'amount_lcy' => 'decimal:2',
|
||||||
|
'debit_amount' => 'decimal:2',
|
||||||
|
'credit_amount' => 'decimal:2',
|
||||||
|
'closing_balance' => 'decimal:2'
|
||||||
|
];
|
||||||
|
}
|
||||||
31
app/Providers/BalanceServiceProvider.php
Normal file
31
app/Providers/BalanceServiceProvider.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Webstatement\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Modules\Webstatement\Services\AccountBalanceService;
|
||||||
|
|
||||||
|
class BalanceServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register the service provider.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$this->app->singleton(AccountBalanceService::class, function ($app) {
|
||||||
|
return new AccountBalanceService();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the services provided by the provider.
|
||||||
|
*
|
||||||
|
* @return array<string>
|
||||||
|
*/
|
||||||
|
public function provides(): array
|
||||||
|
{
|
||||||
|
return [AccountBalanceService::class];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ use Modules\Webstatement\Console\{
|
|||||||
CombinePdf,
|
CombinePdf,
|
||||||
ConvertHtmlToPdf,
|
ConvertHtmlToPdf,
|
||||||
ExportDailyStatements,
|
ExportDailyStatements,
|
||||||
ProcessDailyMigration,
|
ProcessDailyStaging,
|
||||||
ExportPeriodStatements,
|
ExportPeriodStatements,
|
||||||
UpdateAllAtmCardsCommand,
|
UpdateAllAtmCardsCommand,
|
||||||
CheckEmailProgressCommand,
|
CheckEmailProgressCommand,
|
||||||
@@ -57,6 +57,7 @@ class WebstatementServiceProvider extends ServiceProvider
|
|||||||
{
|
{
|
||||||
$this->app->register(EventServiceProvider::class);
|
$this->app->register(EventServiceProvider::class);
|
||||||
$this->app->register(RouteServiceProvider::class);
|
$this->app->register(RouteServiceProvider::class);
|
||||||
|
$this->app->register(BalanceServiceProvider::class);
|
||||||
$this->app->bind(UpdateAtmCardBranchCurrencyJob::class);
|
$this->app->bind(UpdateAtmCardBranchCurrencyJob::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +69,7 @@ class WebstatementServiceProvider extends ServiceProvider
|
|||||||
$this->commands([
|
$this->commands([
|
||||||
GenerateBiayakartuCommand::class,
|
GenerateBiayakartuCommand::class,
|
||||||
GenerateBiayaKartuCsvCommand::class,
|
GenerateBiayaKartuCsvCommand::class,
|
||||||
ProcessDailyMigration::class,
|
ProcessDailyStaging::class,
|
||||||
ExportDailyStatements::class,
|
ExportDailyStatements::class,
|
||||||
CombinePdf::class,
|
CombinePdf::class,
|
||||||
ConvertHtmlToPdf::class,
|
ConvertHtmlToPdf::class,
|
||||||
|
|||||||
130
app/Services/AccountBalanceService.php
Normal file
130
app/Services/AccountBalanceService.php
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Webstatement\Services;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Modules\Webstatement\Models\AccountBalance;
|
||||||
|
use Modules\Webstatement\Models\StmtEntry;
|
||||||
|
|
||||||
|
class AccountBalanceService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get balance summary (opening and closing balance)
|
||||||
|
*
|
||||||
|
* @param string $accountNumber
|
||||||
|
* @param string $startDate
|
||||||
|
* @param string $endDate
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getBalanceSummary(string $accountNumber, string $startDate, string $endDate): array
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($accountNumber, $startDate, $endDate) {
|
||||||
|
Log::info('Calculating balance summary', [
|
||||||
|
'account_number' => $accountNumber,
|
||||||
|
'start_date' => $startDate,
|
||||||
|
'end_date' => $endDate
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Convert dates to Carbon instances
|
||||||
|
$startDateCarbon = Carbon::parse($startDate);
|
||||||
|
$endDateCarbon = Carbon::parse($endDate);
|
||||||
|
|
||||||
|
// Get opening balance (balance from previous day)
|
||||||
|
$openingBalanceDate = $startDateCarbon->copy()->subDay();
|
||||||
|
$openingBalance = $this->getAccountBalance($accountNumber, $openingBalanceDate);
|
||||||
|
|
||||||
|
// Get closing balance date (previous day from end date)
|
||||||
|
$closingBalanceDate = $endDateCarbon->copy()->subDay();
|
||||||
|
$closingBalanceBase = $this->getAccountBalance($accountNumber, $closingBalanceDate);
|
||||||
|
|
||||||
|
// Get transactions on end date
|
||||||
|
$transactionsOnEndDate = $this->getTransactionsOnDate($accountNumber, $endDate);
|
||||||
|
|
||||||
|
// Calculate closing balance
|
||||||
|
$closingBalance = $closingBalanceBase + $transactionsOnEndDate;
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'account_number' => $accountNumber,
|
||||||
|
'period' => [
|
||||||
|
'start_date' => $startDate,
|
||||||
|
'end_date' => $endDate
|
||||||
|
],
|
||||||
|
'opening_balance' => [
|
||||||
|
'date' => $openingBalanceDate->format('Y-m-d'),
|
||||||
|
'balance' => $openingBalance,
|
||||||
|
'formatted_balance' => number_format($openingBalance, 2)
|
||||||
|
],
|
||||||
|
'closing_balance' => [
|
||||||
|
'date' => $endDate,
|
||||||
|
'balance' => $closingBalance,
|
||||||
|
'formatted_balance' => number_format($closingBalance, 2),
|
||||||
|
'base_balance' => [
|
||||||
|
'date' => $closingBalanceDate->format('Y-m-d'),
|
||||||
|
'balance' => $closingBalanceBase,
|
||||||
|
'formatted_balance' => number_format($closingBalanceBase, 2)
|
||||||
|
],
|
||||||
|
'transactions_on_end_date' => $transactionsOnEndDate,
|
||||||
|
'formatted_transactions_on_end_date' => number_format($transactionsOnEndDate, 2)
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
Log::info('Balance summary calculated successfully', $result);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get account balance for specific date
|
||||||
|
*
|
||||||
|
* @param string $accountNumber
|
||||||
|
* @param Carbon $date
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
private function getAccountBalance(string $accountNumber, Carbon $date): float
|
||||||
|
{
|
||||||
|
$balance = AccountBalance::where('account_number', $accountNumber)
|
||||||
|
->where('period', $date->format('Ymd'))
|
||||||
|
->value('actual_balance');
|
||||||
|
|
||||||
|
if ($balance === null) {
|
||||||
|
Log::warning('Account balance not found', [
|
||||||
|
'account_number' => $accountNumber,
|
||||||
|
'date' => $date->format('Y-m-d'),
|
||||||
|
'period' => $date->format('Ymd')
|
||||||
|
]);
|
||||||
|
return 0.00;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (float) $balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get transactions on specific date
|
||||||
|
*
|
||||||
|
* @param string $accountNumber
|
||||||
|
* @param string $date
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
private function getTransactionsOnDate(string $accountNumber, string $date): float
|
||||||
|
{
|
||||||
|
$total = StmtEntry::where('account_number', $accountNumber)
|
||||||
|
->whereDate('value_date', $date)
|
||||||
|
->sum(DB::raw('CAST(amount_lcy AS DECIMAL(15,2))'));
|
||||||
|
|
||||||
|
return (float) $total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate if account exists
|
||||||
|
*
|
||||||
|
* @param string $accountNumber
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function validateAccount(string $accountNumber): bool
|
||||||
|
{
|
||||||
|
return AccountBalance::where('account_number', $accountNumber)->exists();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,4 +5,17 @@ return [
|
|||||||
|
|
||||||
// ZIP file password configuration
|
// ZIP file password configuration
|
||||||
'zip_password' => env('WEBSTATEMENT_ZIP_PASSWORD', 'statement123'),
|
'zip_password' => env('WEBSTATEMENT_ZIP_PASSWORD', 'statement123'),
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| API Configuration
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| These configuration values are used for API authentication using HMAC
|
||||||
|
| signature validation. These keys are used to validate incoming API
|
||||||
|
| requests and ensure secure communication.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'api_key' => env('API_KEY'),
|
||||||
|
'secret_key' => env('SECRET_KEY'),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('processed_closing_balances', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('account_number', 20)->index();
|
||||||
|
$table->string('period', 8)->index();
|
||||||
|
$table->string('group_name', 20)->default('DEFAULT')->index();
|
||||||
|
$table->integer('sequence_no');
|
||||||
|
$table->string('trans_reference', 50)->nullable();
|
||||||
|
$table->string('booking_date', 8)->nullable();
|
||||||
|
$table->string('transaction_date', 20)->nullable();
|
||||||
|
$table->decimal('amount_lcy', 15, 2)->nullable();
|
||||||
|
$table->string('debit_acct_no', 20)->nullable();
|
||||||
|
$table->string('debit_value_date', 8)->nullable();
|
||||||
|
$table->decimal('debit_amount', 15, 2)->nullable();
|
||||||
|
$table->string('credit_acct_no', 20)->nullable();
|
||||||
|
$table->string('bif_rcv_acct', 20)->nullable();
|
||||||
|
$table->string('bif_rcv_name', 100)->nullable();
|
||||||
|
$table->string('credit_value_date', 8)->nullable();
|
||||||
|
$table->decimal('credit_amount', 15, 2)->nullable();
|
||||||
|
$table->string('at_unique_id', 50)->nullable();
|
||||||
|
$table->string('bif_ref_no', 50)->nullable();
|
||||||
|
$table->string('atm_order_id', 50)->nullable();
|
||||||
|
$table->string('recipt_no', 50)->nullable();
|
||||||
|
$table->string('api_iss_acct', 20)->nullable();
|
||||||
|
$table->string('api_benff_acct', 20)->nullable();
|
||||||
|
$table->string('authoriser', 50)->nullable();
|
||||||
|
$table->text('remarks')->nullable();
|
||||||
|
$table->text('payment_details')->nullable();
|
||||||
|
$table->string('ref_no', 50)->nullable();
|
||||||
|
$table->string('merchant_id', 50)->nullable();
|
||||||
|
$table->string('term_id', 50)->nullable();
|
||||||
|
$table->decimal('closing_balance', 15, 2)->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
// Composite index untuk performa query
|
||||||
|
$table->index(['account_number', 'period', 'group_name']);
|
||||||
|
$table->index(['account_number', 'period', 'sequence_no']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('processed_closing_balances');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('processed_closing_balances', function (Blueprint $table) {
|
||||||
|
$table->string('unique_hash')->after('id')->unique();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('processed_closing_balances', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('unique_hash');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
* Menambahkan field-field yang belum ada pada tabel customers
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('customers', function (Blueprint $table) {
|
||||||
|
// Field yang belum ada berdasarkan CSV header
|
||||||
|
$table->string('ktp_kelurahan')->nullable()->after('local_ref')->comment('Kelurahan sesuai KTP');
|
||||||
|
$table->string('ktp_kecamatan')->nullable()->after('ktp_kelurahan')->comment('Kecamatan sesuai KTP');
|
||||||
|
$table->string('town_country')->nullable()->after('ktp_kecamatan')->comment('Kota/Negara');
|
||||||
|
$table->string('ktp_provinsi')->nullable()->after('town_country')->comment('Provinsi sesuai KTP');
|
||||||
|
$table->string('post_code')->nullable()->after('ktp_provinsi')->comment('Kode pos alternatif');
|
||||||
|
$table->string('l_dom_street')->nullable()->after('post_code')->comment('Alamat domisili - jalan');
|
||||||
|
$table->string('l_dom_rt')->nullable()->after('l_dom_street')->comment('Alamat domisili - RT');
|
||||||
|
$table->string('l_dom_kelurahan')->nullable()->after('l_dom_rt')->comment('Alamat domisili - kelurahan');
|
||||||
|
$table->string('l_dom_rw')->nullable()->after('l_dom_kelurahan')->comment('Alamat domisili - RW');
|
||||||
|
$table->string('l_dom_kecamatan')->nullable()->after('l_dom_rw')->comment('Alamat domisili - kecamatan');
|
||||||
|
$table->string('l_dom_provinsi')->nullable()->after('l_dom_kecamatan')->comment('Alamat domisili - provinsi');
|
||||||
|
$table->string('l_dom_t_country')->nullable()->after('l_dom_provinsi')->comment('Alamat domisili - kota/negara');
|
||||||
|
$table->string('l_dom_post_code')->nullable()->after('l_dom_t_country')->comment('Alamat domisili - kode pos');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
* Menghapus field-field yang ditambahkan
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('customers', function (Blueprint $table) {
|
||||||
|
$table->dropColumn([
|
||||||
|
'ktp_kelurahan',
|
||||||
|
'ktp_kecamatan',
|
||||||
|
'town_country',
|
||||||
|
'ktp_provinsi',
|
||||||
|
'post_code',
|
||||||
|
'l_dom_street',
|
||||||
|
'l_dom_rt',
|
||||||
|
'l_dom_kelurahan',
|
||||||
|
'l_dom_rw',
|
||||||
|
'l_dom_kecamatan',
|
||||||
|
'l_dom_provinsi',
|
||||||
|
'l_dom_t_country',
|
||||||
|
'l_dom_post_code'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -3,7 +3,13 @@
|
|||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Modules\Webstatement\Http\Controllers\CustomerController;
|
use Modules\Webstatement\Http\Controllers\CustomerController;
|
||||||
use Modules\Webstatement\Http\Controllers\EmailBlastController;
|
use Modules\Webstatement\Http\Controllers\EmailBlastController;
|
||||||
|
use Modules\Webstatement\Http\Controllers\Api\AccountBalanceController;
|
||||||
|
|
||||||
Route::post('/email-blast', [EmailBlastController::class, 'sendEmailBlast']);
|
Route::post('/email-blast', [EmailBlastController::class, 'sendEmailBlast']);
|
||||||
Route::get('/email-blast-history', [EmailBlastController::class, 'getEmailBlastHistory']);
|
Route::get('/email-blast-history', [EmailBlastController::class, 'getEmailBlastHistory']);
|
||||||
Route::get('/customers/search', [CustomerController::class, 'search']);
|
Route::get('/customers/search', [CustomerController::class, 'search']);
|
||||||
|
|
||||||
|
// Account Balance API Routes
|
||||||
|
Route::prefix('balance')->group(function () {
|
||||||
|
Route::post('/', [AccountBalanceController::class, 'getBalanceSummary']);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user