Compare commits
21 Commits
master
...
7af5bf2fe5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7af5bf2fe5 | ||
|
|
8a6469ecc9 | ||
|
|
aae0c4ab15 | ||
|
|
150d52f8da | ||
|
|
8736ccf5f8 | ||
|
|
710cbb5232 | ||
|
|
13e077073b | ||
|
|
eff951c600 | ||
|
|
6ad5aff358 | ||
|
|
bd72eb7dfa | ||
|
|
8eb7e69b21 | ||
|
|
4ee5c2e419 | ||
|
|
ca92f32ccb | ||
|
|
e1740c0850 | ||
|
|
d88f4a242e | ||
|
|
c0e5ddd37a | ||
|
|
5f9a82ec20 | ||
|
|
33b1255dfb | ||
|
|
aff6039b33 | ||
|
|
51e432c74f | ||
|
|
9cdc7f9487 |
@@ -4,8 +4,13 @@
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Modules\Webstatement\Http\Controllers\WebstatementController;
|
||||
|
||||
/**
|
||||
* Console command untuk export daily statements
|
||||
* Command ini dapat dijalankan secara manual atau dijadwalkan
|
||||
*/
|
||||
class ExportDailyStatements extends Command
|
||||
{
|
||||
/**
|
||||
@@ -13,38 +18,70 @@
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'webstatement:export-statements';
|
||||
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';
|
||||
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');
|
||||
|
||||
// Log start of process
|
||||
Log::info('Starting daily statement export process', [
|
||||
'queue_name' => $queueName ?? 'default',
|
||||
'command' => 'webstatement:export-statements'
|
||||
]);
|
||||
|
||||
$this->info('Starting daily statement export process...');
|
||||
$this->info('Queue Name: ' . ($queueName ?? 'default'));
|
||||
|
||||
try {
|
||||
$controller = app(WebstatementController::class);
|
||||
$response = $controller->index();
|
||||
|
||||
// Pass queue name to controller if needed
|
||||
// Jika controller membutuhkan queue name, bisa ditambahkan sebagai parameter
|
||||
$response = $controller->index($queueName);
|
||||
|
||||
$responseData = json_decode($response->getContent(), true);
|
||||
$this->info($responseData['message']);
|
||||
$message = $responseData['message'] ?? 'Export process completed';
|
||||
|
||||
$this->info($message);
|
||||
|
||||
// Display summary of jobs queued
|
||||
$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) {
|
||||
$this->error('Error exporting statements: ' . $e->getMessage());
|
||||
$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
|
||||
{account_number : Nomor rekening untuk generate laporan}
|
||||
{period : Period laporan format YYYYMMDD, contoh: 20250515}
|
||||
{group=DEFAULT : Group transaksi QRIS atau DEFAULT}
|
||||
{--user_id=1 : ID user yang menjalankan command (default: 1)}';
|
||||
|
||||
/**
|
||||
@@ -32,7 +33,7 @@ class GenerateClosingBalanceReportCommand extends Command
|
||||
*
|
||||
* @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.
|
||||
@@ -47,10 +48,11 @@ class GenerateClosingBalanceReportCommand extends Command
|
||||
// Get parameters
|
||||
$accountNumber = $this->argument('account_number');
|
||||
$period = $this->argument('period');
|
||||
$group = $this->argument('group');
|
||||
$userId = $this->option('user_id');
|
||||
|
||||
// Validate parameters
|
||||
if (!$this->validateParameters($accountNumber, $period, $userId)) {
|
||||
if (!$this->validateParameters($accountNumber, $period, $group, $userId)) {
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
@@ -61,12 +63,13 @@ class GenerateClosingBalanceReportCommand extends Command
|
||||
Log::info('Console command: Starting closing balance report generation', [
|
||||
'account_number' => $accountNumber,
|
||||
'period' => $period,
|
||||
'group' => $group,
|
||||
'user_id' => $userId,
|
||||
'command' => 'webstatement:generate-closing-balance-report'
|
||||
]);
|
||||
|
||||
// Create report log entry
|
||||
$reportLog = $this->createReportLog($accountNumber, $period, $userId);
|
||||
$reportLog = $this->createReportLog($accountNumber, $period, $group, $userId);
|
||||
|
||||
if (!$reportLog) {
|
||||
$this->error('Failed to create report log entry');
|
||||
@@ -74,14 +77,15 @@ class GenerateClosingBalanceReportCommand extends Command
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
// Dispatch the job
|
||||
GenerateClosingBalanceReportJob::dispatch($accountNumber, $period, $reportLog->id);
|
||||
// Dispatch the job with group parameter
|
||||
GenerateClosingBalanceReportJob::dispatch($accountNumber, $period, $reportLog->id, $group);
|
||||
|
||||
DB::commit();
|
||||
|
||||
$this->info("Closing Balance report generation job queued successfully!");
|
||||
$this->info("Account Number: {$accountNumber}");
|
||||
$this->info("Period: {$period}");
|
||||
$this->info("Group: {$group}");
|
||||
$this->info("Report Log ID: {$reportLog->id}");
|
||||
$this->info('The report will be generated in the background.');
|
||||
$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', [
|
||||
'account_number' => $accountNumber,
|
||||
'period' => $period,
|
||||
'group' => $group,
|
||||
'report_log_id' => $reportLog->id,
|
||||
'user_id' => $userId
|
||||
]);
|
||||
@@ -105,6 +110,7 @@ class GenerateClosingBalanceReportCommand extends Command
|
||||
Log::error('Console command: Error generating closing balance report', [
|
||||
'account_number' => $accountNumber,
|
||||
'period' => $period,
|
||||
'group' => $group,
|
||||
'user_id' => $userId,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
@@ -120,10 +126,11 @@ class GenerateClosingBalanceReportCommand extends Command
|
||||
*
|
||||
* @param string $accountNumber
|
||||
* @param string $period
|
||||
* @param string $group
|
||||
* @param int $userId
|
||||
* @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
|
||||
if (empty($accountNumber)) {
|
||||
@@ -147,6 +154,13 @@ class GenerateClosingBalanceReportCommand extends Command
|
||||
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
|
||||
$user = User::find($userId);
|
||||
if (!$user) {
|
||||
@@ -163,10 +177,11 @@ class GenerateClosingBalanceReportCommand extends Command
|
||||
*
|
||||
* @param string $accountNumber
|
||||
* @param string $period
|
||||
* @param string $group
|
||||
* @param int $userId
|
||||
* @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 {
|
||||
// Convert period string to Carbon date
|
||||
@@ -176,6 +191,7 @@ class GenerateClosingBalanceReportCommand extends Command
|
||||
'account_number' => $accountNumber,
|
||||
'period' => $period,
|
||||
'report_date' => $reportDate, // Required field yang sebelumnya missing
|
||||
'group_name' => strtoupper($group), // Tambahkan group_name ke log
|
||||
'status' => 'pending',
|
||||
'user_id' => $userId,
|
||||
'created_by' => $userId, // Required field yang sebelumnya missing
|
||||
@@ -190,6 +206,7 @@ class GenerateClosingBalanceReportCommand extends Command
|
||||
'report_log_id' => $reportLog->id,
|
||||
'account_number' => $accountNumber,
|
||||
'period' => $period,
|
||||
'group' => $group,
|
||||
'report_date' => $reportDate->format('Y-m-d'),
|
||||
'user_id' => $userId
|
||||
]);
|
||||
@@ -200,6 +217,7 @@ class GenerateClosingBalanceReportCommand extends Command
|
||||
Log::error('Console command: Error creating report log', [
|
||||
'account_number' => $accountNumber,
|
||||
'period' => $period,
|
||||
'group' => $group,
|
||||
'user_id' => $userId,
|
||||
'error' => $e->getMessage(),
|
||||
'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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,17 +5,29 @@
|
||||
use App\Http\Controllers\Controller;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Illuminate\Http\Request;
|
||||
use Modules\Webstatement\Jobs\ExportStatementJob;
|
||||
use Modules\Webstatement\Models\AccountBalance;
|
||||
use Modules\Webstatement\Jobs\ExportStatementPeriodJob;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
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()
|
||||
public function index(Request $request)
|
||||
{
|
||||
$queueName = $request->get('queue_name', 'default');
|
||||
|
||||
Log::info('Starting statement export process', [
|
||||
'queue_name' => $queueName
|
||||
]);
|
||||
|
||||
$jobIds = [];
|
||||
$data = [];
|
||||
|
||||
@@ -28,24 +40,34 @@
|
||||
$this->getAccountBalance($accountNumber, $period),
|
||||
$clientName // Pass the client name to the job
|
||||
);
|
||||
$jobIds[] = app(Dispatcher::class)->dispatch($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
|
||||
'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)
|
||||
@@ -142,8 +164,18 @@
|
||||
}
|
||||
|
||||
|
||||
function printStatementRekening($accountNumber, $period = null) {
|
||||
/**
|
||||
* 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')
|
||||
@@ -159,21 +191,28 @@
|
||||
$clientName = 'client1';
|
||||
|
||||
try {
|
||||
\Log::info("Starting statement export for account: {$accountNumber}, period: {$period}, client: {$clientName}");
|
||||
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
|
||||
$job = ExportStatementPeriodJob::dispatch($accountNumber, $period, $balance, $clientName);
|
||||
// Dispatch the job dengan queue name yang spesifik
|
||||
$job = ExportStatementPeriodJob::dispatch($accountNumber, $period, $balance, $clientName)
|
||||
->onQueue($queueName);
|
||||
|
||||
\Log::info("Statement export job dispatched successfully", [
|
||||
Log::info("Statement export job dispatched successfully", [
|
||||
'job_id' => $job->job_id ?? null,
|
||||
'account' => $accountNumber,
|
||||
'period' => $period,
|
||||
'client' => $clientName
|
||||
'client' => $clientName,
|
||||
'queue_name' => $queueName
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
@@ -183,21 +222,24 @@
|
||||
'job_id' => $job->job_id ?? null,
|
||||
'account_number' => $accountNumber,
|
||||
'period' => $period,
|
||||
'client_name' => $clientName
|
||||
'client_name' => $clientName,
|
||||
'queue_name' => $queueName
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::error("Failed to export statement", [
|
||||
Log::error("Failed to export statement", [
|
||||
'error' => $e->getMessage(),
|
||||
'account' => $accountNumber,
|
||||
'period' => $period
|
||||
'period' => $period,
|
||||
'queue_name' => $queueName
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to queue statement export job',
|
||||
'error' => $e->getMessage()
|
||||
'error' => $e->getMessage(),
|
||||
'queue_name' => $queueName
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,26 +2,22 @@
|
||||
|
||||
namespace Modules\Webstatement\Jobs;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Modules\Webstatement\Models\AccountBalance;
|
||||
use Modules\Webstatement\Models\ClosingBalanceReportLog;
|
||||
use Modules\Webstatement\Models\StmtEntry;
|
||||
use Modules\Webstatement\Models\StmtEntryDetail;
|
||||
use Modules\Webstatement\Models\TempFundsTransfer;
|
||||
use Modules\Webstatement\Models\DataCapture;
|
||||
use Illuminate\Queue\{InteractsWithQueue, SerializesModels};
|
||||
use Illuminate\Support\Facades\{DB, Log, Storage};
|
||||
use Modules\Webstatement\Models\{AccountBalance,
|
||||
ClosingBalanceReportLog,
|
||||
ProcessedClosingBalance,
|
||||
StmtEntry,
|
||||
StmtEntryDetail};
|
||||
|
||||
/**
|
||||
* Job untuk generate laporan closing balance
|
||||
* Mengambil data transaksi dan menghitung closing balance
|
||||
* Job untuk generate laporan closing balance dengan optimasi performa
|
||||
* Menggunakan database staging sebelum export CSV
|
||||
*/
|
||||
class GenerateClosingBalanceReportJob implements ShouldQueue
|
||||
{
|
||||
@@ -36,10 +32,6 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param string $accountNumber
|
||||
* @param string $period
|
||||
* @param int $reportLogId
|
||||
*/
|
||||
public function __construct(string $accountNumber, string $period, int $reportLogId, string $groupName = 'DEFAULT')
|
||||
{
|
||||
@@ -50,8 +42,7 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
* Memproses data transaksi dan generate laporan closing balance
|
||||
* Execute the job dengan optimasi performa
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
@@ -63,51 +54,49 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
|
||||
}
|
||||
|
||||
try {
|
||||
Log::info('Starting closing balance report generation', [
|
||||
Log::info('Starting optimized closing balance report generation', [
|
||||
'account_number' => $this->accountNumber,
|
||||
'period' => $this->period,
|
||||
'group_name' => $this->groupName,
|
||||
'report_log_id' => $this->reportLogId
|
||||
]);
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
// Update status to processing
|
||||
$reportLog->update([
|
||||
'status' => 'processing',
|
||||
'updated_at' => now()
|
||||
]);
|
||||
|
||||
// Get opening balance
|
||||
$openingBalance = $this->getOpeningBalance();
|
||||
// Gunakan satu transaksi untuk seluruh proses
|
||||
DB::transaction(function () use ($reportLog) {
|
||||
// Step 1: Process and save to database (fast)
|
||||
$this->processAndSaveClosingBalanceData();
|
||||
|
||||
// Generate report data
|
||||
$reportData = $this->generateReportData($openingBalance);
|
||||
// Step 2: Export from database to CSV (fast)
|
||||
$filePath = $this->exportFromDatabaseToCsv();
|
||||
|
||||
// Export to CSV
|
||||
$filePath = $this->exportToCsv($reportData);
|
||||
// Get record count from database
|
||||
$recordCount = $this->getProcessedRecordCount();
|
||||
|
||||
// Update report log with success
|
||||
$reportLog->update([
|
||||
'status' => 'completed',
|
||||
'file_path' => $filePath,
|
||||
'file_size' => Storage::disk($this->disk)->size($filePath),
|
||||
'record_count' => count($reportData),
|
||||
'record_count' => $recordCount,
|
||||
'updated_at' => now()
|
||||
]);
|
||||
|
||||
DB::commit();
|
||||
|
||||
Log::info('Closing balance report generation completed successfully', [
|
||||
Log::info('Optimized closing balance report generation completed successfully', [
|
||||
'account_number' => $this->accountNumber,
|
||||
'period' => $this->period,
|
||||
'file_path' => $filePath,
|
||||
'record_count' => count($reportData)
|
||||
'record_count' => $recordCount
|
||||
]);
|
||||
});
|
||||
|
||||
} catch (Exception $e) {
|
||||
DB::rollback();
|
||||
|
||||
Log::error('Error generating closing balance report', [
|
||||
Log::error('Error generating optimized closing balance report', [
|
||||
'account_number' => $this->accountNumber,
|
||||
'period' => $this->period,
|
||||
'error' => $e->getMessage(),
|
||||
@@ -124,11 +113,61 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process and save closing balance data to database dengan proteksi duplikasi
|
||||
* Memproses dan menyimpan data closing balance dengan perlindungan terhadap duplikasi
|
||||
*/
|
||||
private function processAndSaveClosingBalanceData(): void
|
||||
{
|
||||
$criteria = [
|
||||
'account_number' => $this->accountNumber,
|
||||
'period' => $this->period,
|
||||
'group_name' => $this->groupName
|
||||
];
|
||||
|
||||
// HAPUS DB::beginTransaction() - sudah ada di handle()
|
||||
|
||||
// Sederhana: hapus data existing terlebih dahulu seperti ExportStatementJob
|
||||
$this->deleteExistingProcessedData($criteria);
|
||||
|
||||
// Get opening balance
|
||||
$runningBalance = $this->getOpeningBalance();
|
||||
$sequenceNo = 0;
|
||||
|
||||
Log::info('Starting to process closing balance data', [
|
||||
'opening_balance' => $runningBalance,
|
||||
'criteria' => $criteria
|
||||
]);
|
||||
|
||||
// Build query yang sederhana tanpa eliminasi duplicate rumit
|
||||
$query = $this->buildTransactionQuery();
|
||||
|
||||
// Proses dan insert data dengan batch updateOrCreate untuk efisiensi
|
||||
$query->chunk($this->chunkSize, function ($transactions) use (&$runningBalance, &$sequenceNo) {
|
||||
$processedData = $this->prepareProcessedClosingBalanceData($transactions, $runningBalance, $sequenceNo);
|
||||
|
||||
if (!empty($processedData)) {
|
||||
// Gunakan batch processing untuk updateOrCreate
|
||||
$this->batchUpdateOrCreate($processedData);
|
||||
}
|
||||
});
|
||||
|
||||
// HAPUS DB::commit() - akan di-handle di handle()
|
||||
|
||||
$recordCount = $this->getProcessedRecordCount();
|
||||
Log::info('Closing balance data processing completed successfully', [
|
||||
'final_sequence' => $sequenceNo,
|
||||
'final_balance' => $runningBalance,
|
||||
'record_count' => $recordCount
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get opening balance from account balance table
|
||||
* Mengambil saldo awal dari tabel account balance
|
||||
*/
|
||||
private function getOpeningBalance(): float
|
||||
private function getOpeningBalance()
|
||||
: float
|
||||
{
|
||||
Log::info('Getting opening balance', [
|
||||
'account_number' => $this->accountNumber,
|
||||
@@ -163,71 +202,42 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
|
||||
}
|
||||
|
||||
/**
|
||||
* Build transaction query using pure Eloquent relationships
|
||||
* Membangun query transaksi menggunakan relasi Eloquent murni
|
||||
* Build transaction query dengan pendekatan sederhana tanpa eliminasi duplicate rumit
|
||||
*/
|
||||
private function buildTransactionQuery()
|
||||
{
|
||||
Log::info('Building transaction query using pure Eloquent relationships', [
|
||||
Log::info('Building transaction query', [
|
||||
'group_name' => $this->groupName,
|
||||
'account_number' => $this->accountNumber,
|
||||
'period' => $this->period
|
||||
]);
|
||||
|
||||
// Tentukan model berdasarkan group name
|
||||
$modelClass = $this->getModelByGroup();
|
||||
|
||||
// Build query menggunakan pure Eloquent dengan eager loading
|
||||
$query = $modelClass::with([
|
||||
'ft' => function($query) {
|
||||
$query->select([
|
||||
'_id',
|
||||
'ref_no',
|
||||
'debit_acct_no',
|
||||
'debit_value_date',
|
||||
'credit_acct_no',
|
||||
'bif_rcv_acct',
|
||||
'bif_rcv_name',
|
||||
'credit_value_date',
|
||||
'at_unique_id',
|
||||
'bif_ref_no',
|
||||
'atm_order_id',
|
||||
'recipt_no',
|
||||
'api_iss_acct',
|
||||
'api_benff_acct',
|
||||
'authoriser',
|
||||
'remarks',
|
||||
'payment_details',
|
||||
'merchant_id',
|
||||
'term_id',
|
||||
'date_time'
|
||||
]);
|
||||
},
|
||||
'dc' => function($query) {
|
||||
$query->select([
|
||||
'id',
|
||||
'date_time'
|
||||
]);
|
||||
}
|
||||
])
|
||||
->select([
|
||||
$query = $modelClass::select([
|
||||
'id',
|
||||
'trans_reference',
|
||||
'booking_date',
|
||||
'amount_lcy',
|
||||
'date_time'
|
||||
])
|
||||
->with([
|
||||
'ft' => function ($query) {
|
||||
$query->select('ref_no', 'date_time', 'debit_acct_no', 'debit_value_date',
|
||||
'credit_acct_no', 'bif_rcv_acct', 'bif_rcv_name', 'credit_value_date',
|
||||
'at_unique_id', 'bif_ref_no', 'atm_order_id', 'recipt_no',
|
||||
'api_iss_acct', 'api_benff_acct', 'authoriser', 'remarks',
|
||||
'payment_details', 'ref_no', 'merchant_id', 'term_id');
|
||||
},
|
||||
'dc' => function ($query) {
|
||||
$query->select('id', 'date_time');
|
||||
}
|
||||
])
|
||||
->where('account_number', $this->accountNumber)
|
||||
->where('booking_date', $this->period)
|
||||
->orderBy('booking_date')
|
||||
->orderBy('date_time');
|
||||
|
||||
Log::info('Transaction query built successfully using pure Eloquent', [
|
||||
'model_class' => $modelClass,
|
||||
'account_number' => $this->accountNumber,
|
||||
'period' => $this->period
|
||||
]);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
@@ -251,11 +261,76 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare processed closing balance data tanpa validasi duplikasi
|
||||
*/
|
||||
private function prepareProcessedClosingBalanceData($transactions, &$runningBalance, &$sequenceNo)
|
||||
: array
|
||||
{
|
||||
$processedData = [];
|
||||
|
||||
foreach ($transactions as $transaction) {
|
||||
$sequenceNo++;
|
||||
|
||||
// Process transaction data
|
||||
$processedTransactionData = $this->processTransactionData($transaction);
|
||||
|
||||
// Update running balance
|
||||
$amount = (float) $transaction->amount_lcy;
|
||||
$runningBalance += $amount;
|
||||
|
||||
// Format transaction date
|
||||
$transactionDate = $this->formatDateTime($processedTransactionData['date_time']);
|
||||
|
||||
// Prepare data untuk database insert tanpa unique_hash
|
||||
$processedData[] = [
|
||||
'account_number' => $this->accountNumber,
|
||||
'period' => $this->period,
|
||||
'group_name' => $this->groupName,
|
||||
'sequence_no' => $sequenceNo,
|
||||
'trans_reference' => $processedTransactionData['trans_reference'],
|
||||
'booking_date' => $processedTransactionData['booking_date'],
|
||||
'transaction_date' => $transactionDate,
|
||||
'amount_lcy' => $processedTransactionData['amount_lcy'],
|
||||
'debit_acct_no' => $processedTransactionData['debit_acct_no'],
|
||||
'debit_value_date' => $processedTransactionData['debit_value_date'],
|
||||
'debit_amount' => $processedTransactionData['debit_amount'],
|
||||
'credit_acct_no' => $processedTransactionData['credit_acct_no'],
|
||||
'bif_rcv_acct' => $processedTransactionData['bif_rcv_acct'],
|
||||
'bif_rcv_name' => $processedTransactionData['bif_rcv_name'],
|
||||
'credit_value_date' => $processedTransactionData['credit_value_date'],
|
||||
'credit_amount' => $processedTransactionData['credit_amount'],
|
||||
'at_unique_id' => $processedTransactionData['at_unique_id'],
|
||||
'bif_ref_no' => $processedTransactionData['bif_ref_no'],
|
||||
'atm_order_id' => $processedTransactionData['atm_order_id'],
|
||||
'recipt_no' => $processedTransactionData['recipt_no'],
|
||||
'api_iss_acct' => $processedTransactionData['api_iss_acct'],
|
||||
'api_benff_acct' => $processedTransactionData['api_benff_acct'],
|
||||
'authoriser' => $processedTransactionData['authoriser'],
|
||||
'remarks' => $processedTransactionData['remarks'],
|
||||
'payment_details' => $processedTransactionData['payment_details'],
|
||||
'ref_no' => $processedTransactionData['ref_no'],
|
||||
'merchant_id' => $processedTransactionData['merchant_id'],
|
||||
'term_id' => $processedTransactionData['term_id'],
|
||||
'closing_balance' => $runningBalance,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
];
|
||||
}
|
||||
|
||||
Log::info('Processed closing balance data prepared', [
|
||||
'total_records' => count($processedData)
|
||||
]);
|
||||
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process transaction data from ORM result
|
||||
* Memproses data transaksi dari hasil ORM
|
||||
*/
|
||||
private function processTransactionData($transaction): array
|
||||
private function processTransactionData($transaction)
|
||||
: array
|
||||
{
|
||||
Log::info('Processing transaction data', [
|
||||
'trans_reference' => $transaction->trans_reference,
|
||||
@@ -310,174 +385,12 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updated generateReportData method using pure ORM
|
||||
* Method generateReportData yang diperbarui menggunakan ORM murni
|
||||
*/
|
||||
private function generateReportData(): array
|
||||
{
|
||||
Log::info('Starting report data generation using pure ORM', [
|
||||
'account_number' => $this->accountNumber,
|
||||
'period' => $this->period,
|
||||
'group_name' => $this->groupName,
|
||||
'chunk_size' => $this->chunkSize
|
||||
]);
|
||||
|
||||
$reportData = [];
|
||||
$runningBalance = $this->getOpeningBalance();
|
||||
$sequenceNo = 1;
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
// Build query menggunakan pure ORM
|
||||
$query = $this->buildTransactionQuery();
|
||||
|
||||
// Process data dalam chunks untuk efisiensi memory
|
||||
$query->chunk($this->chunkSize, function ($transactions) use (&$reportData, &$runningBalance, &$sequenceNo) {
|
||||
Log::info('Processing transaction chunk', [
|
||||
'chunk_size' => $transactions->count(),
|
||||
'current_sequence' => $sequenceNo,
|
||||
'current_balance' => $runningBalance
|
||||
]);
|
||||
|
||||
foreach ($transactions as $transaction) {
|
||||
// Process transaction data
|
||||
$processedData = $this->processTransactionData($transaction);
|
||||
|
||||
// Update running balance
|
||||
$amount = (float) $transaction->amount_lcy;
|
||||
$runningBalance += $amount;
|
||||
|
||||
// Format transaction date
|
||||
$transactionDate = $this->formatDateTime($processedData['date_time']);
|
||||
|
||||
// Build report data row
|
||||
$reportData[] = $this->buildReportDataRow(
|
||||
(object) $processedData,
|
||||
$sequenceNo,
|
||||
$transactionDate,
|
||||
$runningBalance
|
||||
);
|
||||
|
||||
$sequenceNo++;
|
||||
}
|
||||
|
||||
Log::info('Chunk processed successfully', [
|
||||
'processed_count' => $transactions->count(),
|
||||
'total_records_so_far' => count($reportData),
|
||||
'current_balance' => $runningBalance
|
||||
]);
|
||||
});
|
||||
|
||||
DB::commit();
|
||||
|
||||
Log::info('Report data generation completed using pure ORM', [
|
||||
'total_records' => count($reportData),
|
||||
'final_balance' => $runningBalance,
|
||||
'final_sequence' => $sequenceNo - 1
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
DB::rollback();
|
||||
|
||||
Log::error('Error generating report data using pure ORM', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'account_number' => $this->accountNumber,
|
||||
'period' => $this->period
|
||||
]);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $reportData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table name based on group name
|
||||
* Mendapatkan nama tabel berdasarkan group name
|
||||
*/
|
||||
private function getTableNameByGroup(): string
|
||||
{
|
||||
return $this->groupName === 'QRIS' ? 'stmt_entry' : 'stmt_entry_details';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get select fields for the query
|
||||
* Mendapatkan field select untuk query
|
||||
*/
|
||||
private function getSelectFields(): array
|
||||
{
|
||||
return [
|
||||
's.trans_reference',
|
||||
's.booking_date',
|
||||
's.amount_lcy',
|
||||
'ft.debit_acct_no',
|
||||
'ft.debit_value_date',
|
||||
DB::raw('CASE WHEN s.amount_lcy::numeric < 0 THEN s.amount_lcy::numeric ELSE NULL END AS debit_amount'),
|
||||
'ft.credit_acct_no',
|
||||
'ft.bif_rcv_acct',
|
||||
'ft.bif_rcv_name',
|
||||
'ft.credit_value_date',
|
||||
DB::raw('CASE WHEN s.amount_lcy::numeric > 0 THEN s.amount_lcy::numeric ELSE NULL END AS credit_amount'),
|
||||
'ft.at_unique_id',
|
||||
'ft.bif_ref_no',
|
||||
'ft.atm_order_id',
|
||||
'ft.recipt_no',
|
||||
'ft.api_iss_acct',
|
||||
'ft.api_benff_acct',
|
||||
DB::raw('COALESCE(ft.date_time, dc.date_time, s.date_time) AS date_time'),
|
||||
'ft.authoriser',
|
||||
'ft.remarks',
|
||||
'ft.payment_details',
|
||||
'ft.ref_no',
|
||||
'ft.merchant_id',
|
||||
'ft.term_id'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build report data row from transaction
|
||||
* Membangun baris data laporan dari transaksi
|
||||
*/
|
||||
private function buildReportDataRow($transaction, int $sequenceNo, string $transactionDate, float $runningBalance): array
|
||||
{
|
||||
return [
|
||||
'sequence_no' => $sequenceNo,
|
||||
'trans_reference' => $transaction->trans_reference,
|
||||
'booking_date' => $transaction->booking_date,
|
||||
'transaction_date' => $transactionDate,
|
||||
'amount_lcy' => $transaction->amount_lcy,
|
||||
'debit_acct_no' => $transaction->debit_acct_no,
|
||||
'debit_value_date' => $transaction->debit_value_date,
|
||||
'debit_amount' => $transaction->debit_amount,
|
||||
'credit_acct_no' => $transaction->credit_acct_no,
|
||||
'bif_rcv_acct' => $transaction->bif_rcv_acct,
|
||||
'bif_rcv_name' => $transaction->bif_rcv_name,
|
||||
'credit_value_date' => $transaction->credit_value_date,
|
||||
'credit_amount' => $transaction->credit_amount,
|
||||
'at_unique_id' => $transaction->at_unique_id,
|
||||
'bif_ref_no' => $transaction->bif_ref_no,
|
||||
'atm_order_id' => $transaction->atm_order_id,
|
||||
'recipt_no' => $transaction->recipt_no,
|
||||
'api_iss_acct' => $transaction->api_iss_acct,
|
||||
'api_benff_acct' => $transaction->api_benff_acct,
|
||||
'authoriser' => $transaction->authoriser,
|
||||
'remarks' => $transaction->remarks,
|
||||
'payment_details' => $transaction->payment_details,
|
||||
'ref_no' => $transaction->ref_no,
|
||||
'merchant_id' => $transaction->merchant_id,
|
||||
'term_id' => $transaction->term_id,
|
||||
'closing_balance' => $runningBalance
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format datetime string
|
||||
* Memformat string datetime
|
||||
*/
|
||||
private function formatDateTime(?string $datetime): string
|
||||
private function formatDateTime(?string $datetime)
|
||||
: string
|
||||
{
|
||||
if (!$datetime) {
|
||||
return '';
|
||||
@@ -493,17 +406,16 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
|
||||
return $datetime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export report data to CSV file
|
||||
* Export data laporan ke file CSV
|
||||
* Export from database to CSV (very fast)
|
||||
*/
|
||||
private function exportToCsv(array $reportData): string
|
||||
private function exportFromDatabaseToCsv()
|
||||
: string
|
||||
{
|
||||
Log::info('Starting CSV export for closing balance report', [
|
||||
Log::info('Starting CSV export from database for closing balance report', [
|
||||
'account_number' => $this->accountNumber,
|
||||
'period' => $this->period,
|
||||
'record_count' => count($reportData)
|
||||
'group_name' => $this->groupName
|
||||
]);
|
||||
|
||||
// Create directory structure
|
||||
@@ -514,7 +426,7 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
|
||||
Storage::disk($this->disk)->makeDirectory($accountPath);
|
||||
|
||||
// Generate filename
|
||||
$fileName = "closing_balance_{$this->accountNumber}_{$this->period}.csv";
|
||||
$fileName = "closing_balance_{$this->accountNumber}_{$this->period}_{$this->groupName}.csv";
|
||||
$filePath = "{$accountPath}/{$fileName}";
|
||||
|
||||
// Delete existing file if exists
|
||||
@@ -553,54 +465,177 @@ class GenerateClosingBalanceReportJob implements ShouldQueue
|
||||
];
|
||||
|
||||
$csvContent = implode('|', $csvHeader) . "\n";
|
||||
Storage::disk($this->disk)->put($filePath, $csvContent);
|
||||
|
||||
// Inisialisasi counter untuk sequence number
|
||||
$sequenceCounter = 1;
|
||||
$processedHashes = [];
|
||||
|
||||
ProcessedClosingBalance::where('account_number', $this->accountNumber)
|
||||
->where('period', $this->period)
|
||||
->where('group_name', $this->groupName)
|
||||
->orderBy('sequence_no')
|
||||
->chunk($this->chunkSize, function ($records) use ($filePath, &$sequenceCounter, &$processedHashes) {
|
||||
$csvContent = [];
|
||||
foreach ($records as $record) {
|
||||
// Pengecekan unique_hash: skip jika sudah diproses
|
||||
if (in_array($record->unique_hash, $processedHashes)) {
|
||||
Log::debug('Skipping duplicate unique_hash in CSV export', [
|
||||
'unique_hash' => $record->unique_hash,
|
||||
'trans_reference' => $record->trans_reference
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Tandai unique_hash sebagai sudah diproses
|
||||
$processedHashes[] = $record->unique_hash;
|
||||
|
||||
// Add data rows
|
||||
foreach ($reportData as $row) {
|
||||
$csvRow = [
|
||||
$row['sequence_no'],
|
||||
$row['trans_reference'] ?? '',
|
||||
$row['booking_date'] ?? '',
|
||||
$row['transaction_date'] ?? '',
|
||||
$row['amount_lcy'] ?? '',
|
||||
$row['debit_acct_no'] ?? '',
|
||||
$row['debit_value_date'] ?? '',
|
||||
$row['debit_amount'] ?? '',
|
||||
$row['credit_acct_no'] ?? '',
|
||||
$row['bif_rcv_acct'] ?? '',
|
||||
$row['bif_rcv_name'] ?? '',
|
||||
$row['credit_value_date'] ?? '',
|
||||
$row['credit_amount'] ?? '',
|
||||
$row['at_unique_id'] ?? '',
|
||||
$row['bif_ref_no'] ?? '',
|
||||
$row['atm_order_id'] ?? '',
|
||||
$row['recipt_no'] ?? '',
|
||||
$row['api_iss_acct'] ?? '',
|
||||
$row['api_benff_acct'] ?? '',
|
||||
$row['authoriser'] ?? '',
|
||||
$row['remarks'] ?? '',
|
||||
$row['payment_details'] ?? '',
|
||||
$row['ref_no'] ?? '',
|
||||
$row['merchant_id'] ?? '',
|
||||
$row['term_id'] ?? '',
|
||||
$row['closing_balance'] ?? ''
|
||||
$sequenceCounter++,
|
||||
// Gunakan counter yang bertambah, bukan sequence_no dari database
|
||||
$record->trans_reference ?? '',
|
||||
$record->booking_date ?? '',
|
||||
$record->transaction_date ?? '',
|
||||
$record->amount_lcy ?? '',
|
||||
$record->debit_acct_no ?? '',
|
||||
$record->debit_value_date ?? '',
|
||||
$record->debit_amount ?? '',
|
||||
$record->credit_acct_no ?? '',
|
||||
$record->bif_rcv_acct ?? '',
|
||||
$record->bif_rcv_name ?? '',
|
||||
$record->credit_value_date ?? '',
|
||||
$record->credit_amount ?? '',
|
||||
$record->at_unique_id ?? '',
|
||||
$record->bif_ref_no ?? '',
|
||||
$record->atm_order_id ?? '',
|
||||
$record->recipt_no ?? '',
|
||||
$record->api_iss_acct ?? '',
|
||||
$record->api_benff_acct ?? '',
|
||||
$record->authoriser ?? '',
|
||||
$record->remarks ?? '',
|
||||
$record->payment_details ?? '',
|
||||
$record->ref_no ?? '',
|
||||
$record->merchant_id ?? '',
|
||||
$record->term_id ?? '',
|
||||
$record->closing_balance ?? ''
|
||||
];
|
||||
|
||||
$csvContent .= implode('|', $csvRow) . "\n";
|
||||
}
|
||||
|
||||
// Save file
|
||||
Storage::disk($this->disk)->put($filePath, $csvContent);
|
||||
if (!empty($csvContent)) {
|
||||
Storage::disk($this->disk)->append($filePath, $csvContent);
|
||||
|
||||
Log::debug('CSV content appended', [
|
||||
'records_processed' => substr_count($csvContent, "\n"),
|
||||
'current_sequence' => $sequenceCounter - 1
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
// Verify file creation
|
||||
if (!Storage::disk($this->disk)->exists($filePath)) {
|
||||
throw new Exception("Failed to create CSV file: {$filePath}");
|
||||
}
|
||||
|
||||
Log::info('CSV export completed successfully', [
|
||||
Log::info('CSV export from database completed successfully', [
|
||||
'file_path' => $filePath,
|
||||
'file_size' => Storage::disk($this->disk)->size($filePath)
|
||||
]);
|
||||
|
||||
return $filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get processed record count
|
||||
*/
|
||||
private function getProcessedRecordCount()
|
||||
: int
|
||||
{
|
||||
return ProcessedClosingBalance::where('account_number', $this->accountNumber)
|
||||
->where('period', $this->period)
|
||||
->where('group_name', $this->groupName)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete existing processed data dengan pendekatan sederhana seperti ExportStatementJob
|
||||
*/
|
||||
private function deleteExistingProcessedData(array $criteria)
|
||||
: void
|
||||
{
|
||||
Log::info('Deleting existing processed data', $criteria);
|
||||
|
||||
$deletedCount = ProcessedClosingBalance::where('account_number', $criteria['account_number'])
|
||||
->where('period', $criteria['period'])
|
||||
->delete();
|
||||
|
||||
Log::info('Existing processed data deleted', [
|
||||
'deleted_count' => $deletedCount,
|
||||
'criteria' => $criteria
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch update or create untuk mengurangi jumlah query dan lock
|
||||
* Menggunakan pendekatan yang lebih efisien untuk menghindari max_lock_per_transaction
|
||||
*/
|
||||
private function batchUpdateOrCreate(array $processedData): void
|
||||
{
|
||||
Log::info('Starting batch updateOrCreate', [
|
||||
'batch_size' => count($processedData)
|
||||
]);
|
||||
|
||||
// Kumpulkan semua trans_reference yang akan diproses
|
||||
$transReferences = collect($processedData)->pluck('trans_reference')->toArray();
|
||||
|
||||
// Ambil data yang sudah ada dalam satu query
|
||||
$existingRecords = ProcessedClosingBalance::where('account_number', $this->accountNumber)
|
||||
->where('period', $this->period)
|
||||
->where('group_name', $this->groupName)
|
||||
->whereIn('trans_reference', $transReferences)
|
||||
->get()
|
||||
->keyBy(function ($item) {
|
||||
return $item->trans_reference . '_' . $item->amount_lcy;
|
||||
});
|
||||
|
||||
$toInsert = [];
|
||||
$toUpdate = [];
|
||||
|
||||
foreach ($processedData as $data) {
|
||||
$key = $data['trans_reference'] . '_' . $data['amount_lcy'];
|
||||
|
||||
if ($existingRecords->has($key)) {
|
||||
// Record sudah ada, siapkan untuk update
|
||||
$existingRecord = $existingRecords->get($key);
|
||||
$toUpdate[] = [
|
||||
'id' => $existingRecord->id,
|
||||
'data' => $data
|
||||
];
|
||||
} else {
|
||||
// Record baru, siapkan untuk insert
|
||||
$toInsert[] = $data;
|
||||
}
|
||||
}
|
||||
|
||||
// Batch insert untuk record baru
|
||||
if (!empty($toInsert)) {
|
||||
DB::table('processed_closing_balances')->insert($toInsert);
|
||||
Log::info('Batch insert completed', ['count' => count($toInsert)]);
|
||||
}
|
||||
|
||||
// Batch update untuk record yang sudah ada
|
||||
if (!empty($toUpdate)) {
|
||||
foreach ($toUpdate as $updateItem) {
|
||||
ProcessedClosingBalance::where('id', $updateItem['id'])
|
||||
->update($updateItem['data']);
|
||||
}
|
||||
Log::info('Batch update completed', ['count' => count($toUpdate)]);
|
||||
}
|
||||
|
||||
Log::info('Batch updateOrCreate completed successfully', [
|
||||
'inserted' => count($toInsert),
|
||||
'updated' => count($toUpdate)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ class ProcessAccountDataJob implements ShouldQueue
|
||||
private const CSV_DELIMITER = '~';
|
||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||
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 string $period = '';
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
private const CSV_DELIMITER = '~';
|
||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||
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 string $period = '';
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
private const CSV_DELIMITER = '~';
|
||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||
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 HEADER_MAP = [
|
||||
'id' => 'transaction_id',
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
private const CSV_DELIMITER = '~';
|
||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||
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 string $period = '';
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
private const CSV_DELIMITER = '~';
|
||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||
private const FILENAME = 'ST.CATEGORY.csv';
|
||||
private const DISK_NAME = 'sftpStatement';
|
||||
private const DISK_NAME = 'staging';
|
||||
private const HEADER_MAP = [
|
||||
'id' => 'id_category',
|
||||
'date_time' => 'date_time',
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
private const CSV_DELIMITER = '~';
|
||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||
private const FILENAME = 'ST.COMPANY.csv';
|
||||
private const DISK_NAME = 'sftpStatement';
|
||||
private const DISK_NAME = 'staging';
|
||||
private const FIELD_MAP = [
|
||||
'id' => null, // Not mapped to model
|
||||
'date_time' => null, // Not mapped to model
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Modules\Webstatement\Models\Customer;
|
||||
@@ -19,7 +20,7 @@
|
||||
private const CSV_DELIMITER = '~';
|
||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||
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 string $period = '';
|
||||
@@ -112,13 +113,28 @@
|
||||
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;
|
||||
$chunkCount = 0;
|
||||
|
||||
while (($row = fgetcsv($handle, 0, self::CSV_DELIMITER)) !== false) {
|
||||
$rowCount++;
|
||||
$this->processRow($row, $headers, $rowCount, $filePath);
|
||||
$this->processRow($row, $csvHeaders, $headerMapping, $rowCount, $filePath);
|
||||
|
||||
// Process in chunks to avoid memory issues
|
||||
if (count($this->customerBatch) >= self::CHUNK_SIZE) {
|
||||
@@ -137,16 +153,61 @@
|
||||
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
|
||||
{
|
||||
if (count($headers) !== count($row)) {
|
||||
if (count($csvHeaders) !== count($row)) {
|
||||
Log::warning("Row $rowCount in $filePath has incorrect column count. Expected: " .
|
||||
count($headers) . ", Got: " . count($row));
|
||||
count($csvHeaders) . ", Got: " . count($row));
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -175,12 +236,20 @@
|
||||
|
||||
/**
|
||||
* Save batched records to the database
|
||||
* Menyimpan data customer dalam batch ke database dengan transaksi
|
||||
*/
|
||||
private function saveBatch()
|
||||
: void
|
||||
{
|
||||
if (empty($this->customerBatch)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$batchSize = count($this->customerBatch);
|
||||
Log::info("Starting batch save", ['batch_size' => $batchSize]);
|
||||
|
||||
try {
|
||||
if (!empty($this->customerBatch)) {
|
||||
DB::transaction(function () use ($batchSize) {
|
||||
// Bulk insert/update customers
|
||||
Customer::upsert(
|
||||
$this->customerBatch,
|
||||
@@ -188,14 +257,26 @@
|
||||
array_diff((new Customer())->getFillable(), ['customer_code']) // Update columns
|
||||
);
|
||||
|
||||
// Reset customer batch after processing
|
||||
Log::info("Batch save completed successfully", ['batch_size' => $batchSize]);
|
||||
});
|
||||
|
||||
// Reset customer batch after successful processing
|
||||
$this->customerBatch = [];
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
Log::error("Error in saveBatch: " . $e->getMessage());
|
||||
$this->errorCount += count($this->customerBatch);
|
||||
Log::error("Error in saveBatch", [
|
||||
'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
|
||||
$this->customerBatch = [];
|
||||
|
||||
// Re-throw exception untuk handling di level atas
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
private const CSV_DELIMITER = '~';
|
||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||
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 CSV_HEADERS = [
|
||||
'id',
|
||||
@@ -187,7 +187,7 @@
|
||||
{
|
||||
// Exclude the last field from CSV
|
||||
if (count($row) > 0) {
|
||||
array_pop($row);
|
||||
//array_pop($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 FILENAME = 'ST.FT.TXN.TYPE.CONDITION.csv';
|
||||
private const DISK_NAME = 'sftpStatement';
|
||||
private const DISK_NAME = 'staging';
|
||||
|
||||
private string $period = '';
|
||||
private int $processedCount = 0;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
private const CSV_DELIMITER = '~';
|
||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||
private const FILENAME = 'ST.FUNDS.TRANSFER.csv';
|
||||
private const DISK_NAME = 'sftpStatement';
|
||||
private const DISK_NAME = 'staging';
|
||||
|
||||
private string $period = '';
|
||||
private int $processedCount = 0;
|
||||
|
||||
@@ -20,7 +20,7 @@ class ProcessProvinceDataJob implements ShouldQueue
|
||||
private const CSV_DELIMITER = '~';
|
||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||
private const FILENAME = 'ST.PROVINCE.csv';
|
||||
private const DISK_NAME = 'sftpStatement';
|
||||
private const DISK_NAME = 'staging';
|
||||
|
||||
private string $period = '';
|
||||
private int $processedCount = 0;
|
||||
|
||||
@@ -19,7 +19,7 @@ class ProcessSectorDataJob implements ShouldQueue
|
||||
private const CSV_DELIMITER = '~';
|
||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||
private const FILENAME = 'ST.SECTOR.csv';
|
||||
private const DISK_NAME = 'sftpStatement';
|
||||
private const DISK_NAME = 'staging';
|
||||
|
||||
private string $period = '';
|
||||
private int $processedCount = 0;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
private const CSV_DELIMITER = '~';
|
||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||
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 string $period = '';
|
||||
|
||||
@@ -20,7 +20,7 @@ class ProcessStmtEntryDetailDataJob implements ShouldQueue
|
||||
private const CSV_DELIMITER = '~';
|
||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||
private const FILENAME = 'ST.STMT.ENTRY.DETAIL.csv';
|
||||
private const DISK_NAME = 'sftpStatement';
|
||||
private const DISK_NAME = 'staging';
|
||||
private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage
|
||||
|
||||
private string $period = '';
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
private const CSV_DELIMITER = '~';
|
||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||
private const FILENAME = 'ST.STMT.NARR.FORMAT.csv';
|
||||
private const DISK_NAME = 'sftpStatement';
|
||||
private const DISK_NAME = 'staging';
|
||||
|
||||
private string $period = '';
|
||||
private int $processedCount = 0;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
private const CSV_DELIMITER = '~';
|
||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||
private const FILENAME = 'ST.STMT.NARR.PARAM.csv';
|
||||
private const DISK_NAME = 'sftpStatement';
|
||||
private const DISK_NAME = 'staging';
|
||||
|
||||
private string $period = '';
|
||||
private int $processedCount = 0;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
private const CSV_DELIMITER = '~';
|
||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||
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 HEADER_MAP = [
|
||||
'id' => 'id_teller',
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
private const CSV_DELIMITER = '~';
|
||||
private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds
|
||||
private const FILENAME = 'ST.TRANSACTION.csv';
|
||||
private const DISK_NAME = 'sftpStatement';
|
||||
private const DISK_NAME = 'staging';
|
||||
|
||||
private string $period = '';
|
||||
private int $processedCount = 0;
|
||||
|
||||
@@ -29,8 +29,33 @@ class Customer extends Model
|
||||
'home_rw',
|
||||
'ktp_rt',
|
||||
'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(){
|
||||
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'
|
||||
];
|
||||
}
|
||||
@@ -11,7 +11,7 @@ use Modules\Webstatement\Console\{
|
||||
CombinePdf,
|
||||
ConvertHtmlToPdf,
|
||||
ExportDailyStatements,
|
||||
ProcessDailyMigration,
|
||||
ProcessDailyStaging,
|
||||
ExportPeriodStatements,
|
||||
UpdateAllAtmCardsCommand,
|
||||
CheckEmailProgressCommand,
|
||||
@@ -68,7 +68,7 @@ class WebstatementServiceProvider extends ServiceProvider
|
||||
$this->commands([
|
||||
GenerateBiayakartuCommand::class,
|
||||
GenerateBiayaKartuCsvCommand::class,
|
||||
ProcessDailyMigration::class,
|
||||
ProcessDailyStaging::class,
|
||||
ExportDailyStatements::class,
|
||||
CombinePdf::class,
|
||||
ConvertHtmlToPdf::class,
|
||||
|
||||
@@ -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'
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user