feat(webstatement): tambah console command bulk untuk generate laporan closing balance
- Membuat GenerateClosingBalanceReportBulkCommand untuk bulk processing - Support untuk memproses banyak rekening sekaligus berdasarkan daftar client - Fitur client filter untuk memproses client tertentu saja - Mode dry-run untuk preview rekening yang akan diproses - Progress bar untuk monitoring proses bulk generation - Interactive confirmation sebelum menjalankan job - Error handling per rekening tanpa menghentikan proses keseluruhan - Database transaction terpisah untuk setiap rekening - Comprehensive logging untuk monitoring dan debugging - Detailed summary sebelum dan sesudah pemrosesan - Daftar client dan rekening sama dengan WebstatementController - Integrasi dengan existing GenerateClosingBalanceReportJob - Remarks field untuk tracking bulk generation dengan client info - Validasi parameter lengkap dan user-friendly error messages
This commit is contained in:
532
app/Console/GenerateClosingBalanceReportBulkCommand.php
Normal file
532
app/Console/GenerateClosingBalanceReportBulkCommand.php
Normal file
@@ -0,0 +1,532 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Webstatement\Console;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Modules\Usermanagement\Models\User;
|
||||
use Modules\Webstatement\Jobs\GenerateClosingBalanceReportJob;
|
||||
use Modules\Webstatement\Models\ClosingBalanceReportLog;
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* Console command untuk generate laporan closing balance untuk banyak rekening sekaligus
|
||||
* Command ini dapat dijalankan secara manual atau dijadwalkan
|
||||
* Mendukung periode range dan daftar rekening custom
|
||||
*/
|
||||
class GenerateClosingBalanceReportBulkCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'webstatement:generate-closing-balance-bulk
|
||||
{start_date : Tanggal mulai periode format YYYYMMDD, contoh: 20250512}
|
||||
{end_date : Tanggal akhir periode format YYYYMMDD, contoh: 20250712}
|
||||
{--accounts= : Daftar rekening dipisahkan koma (opsional, jika tidak ada akan gunakan default list)}
|
||||
{--client= : Filter berdasarkan client tertentu (opsional)}
|
||||
{--user_id=1 : ID user yang menjalankan command (default: 1)}
|
||||
{--dry-run : Tampilkan daftar rekening yang akan diproses tanpa menjalankan job}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generate Closing Balance report untuk banyak rekening sekaligus dengan periode range';
|
||||
|
||||
/**
|
||||
* Daftar rekening default yang akan diproses
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $defaultAccounts = [
|
||||
'IDR1723200010001',
|
||||
'IDR1728100010001',
|
||||
'IDR1728200010001',
|
||||
'IDR1733100010001',
|
||||
'IDR1728300010001',
|
||||
'IDR1733100030001',
|
||||
'IDR1723300010001',
|
||||
'IDR1733100020001',
|
||||
'IDR1733100040001',
|
||||
'IDR1733200010001',
|
||||
'IDR1733200020001',
|
||||
'IDR1733500010001',
|
||||
'IDR1733600010001',
|
||||
'IDR1733300010001',
|
||||
'IDR1733400010001',
|
||||
'IDR1354100010001',
|
||||
'IDR1354300010001',
|
||||
'IDR1354400010001',
|
||||
'IDR1728500010001',
|
||||
'IDR1728600010001',
|
||||
'IDR1354500010001',
|
||||
'IDR1354500020001',
|
||||
'IDR1354500030001',
|
||||
'IDR1354500040001',
|
||||
'IDR1354500050001',
|
||||
'IDR1354500060001',
|
||||
'IDR1354500070001',
|
||||
'IDR1354500080001',
|
||||
'IDR1354500090001',
|
||||
'IDR1354500100001',
|
||||
'IDR1720500010001',
|
||||
'1078333878',
|
||||
'1081647484',
|
||||
'1085552121',
|
||||
'1085677889',
|
||||
'1086677889',
|
||||
'IDR1744200010001',
|
||||
'IDR1744300010001',
|
||||
'IDR1744100010001',
|
||||
'IDR1744400010001',
|
||||
'IDR1364100010001',
|
||||
'IDR1723100010001',
|
||||
'IDR1354200010001'
|
||||
];
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
* Menjalankan proses generate laporan closing balance untuk banyak rekening dengan periode range
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Starting Bulk Closing Balance report generation with date range...');
|
||||
|
||||
// Get parameters
|
||||
$startDate = $this->argument('start_date');
|
||||
$endDate = $this->argument('end_date');
|
||||
$accountsOption = $this->option('accounts');
|
||||
$clientFilter = $this->option('client');
|
||||
$userId = $this->option('user_id');
|
||||
$isDryRun = $this->option('dry-run');
|
||||
|
||||
// Validate parameters
|
||||
if (!$this->validateParameters($startDate, $endDate, $userId)) {
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
try {
|
||||
// Get account list
|
||||
$accountList = $this->getAccountList($accountsOption, $clientFilter);
|
||||
|
||||
if (empty($accountList)) {
|
||||
$this->warn('No accounts found for processing.');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
// Generate date range
|
||||
$dateRange = $this->generateDateRange($startDate, $endDate);
|
||||
|
||||
// Show summary
|
||||
$this->showSummary($accountList, $dateRange, $isDryRun);
|
||||
|
||||
if ($isDryRun) {
|
||||
$this->info('Dry run completed. No jobs were dispatched.');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
// Confirm execution
|
||||
if (!$this->confirm('Do you want to proceed with generating reports for all accounts and periods?')) {
|
||||
$this->info('Operation cancelled.');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
// Process accounts for all dates in range
|
||||
$results = $this->processAccountsWithDateRange($accountList, $dateRange, $userId);
|
||||
|
||||
// Show results
|
||||
$this->showResults($results);
|
||||
|
||||
return Command::SUCCESS;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->error('Error in bulk closing balance report generation: ' . $e->getMessage());
|
||||
|
||||
Log::error('Console command: Error in bulk closing balance report generation', [
|
||||
'start_date' => $startDate,
|
||||
'end_date' => $endDate,
|
||||
'client_filter' => $clientFilter,
|
||||
'user_id' => $userId,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate command parameters
|
||||
* Validasi parameter command termasuk validasi range tanggal
|
||||
*
|
||||
* @param string $startDate
|
||||
* @param string $endDate
|
||||
* @param int $userId
|
||||
* @return bool
|
||||
*/
|
||||
private function validateParameters(string $startDate, string $endDate, int $userId): bool
|
||||
{
|
||||
// Validate date format (YYYYMMDD)
|
||||
if (!preg_match('/^\\d{8}$/', $startDate)) {
|
||||
$this->error('Invalid start_date format. Use YYYYMMDD format (example: 20250512)');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!preg_match('/^\\d{8}$/', $endDate)) {
|
||||
$this->error('Invalid end_date format. Use YYYYMMDD format (example: 20250712)');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate start date
|
||||
$startYear = substr($startDate, 0, 4);
|
||||
$startMonth = substr($startDate, 4, 2);
|
||||
$startDay = substr($startDate, 6, 2);
|
||||
|
||||
if (!checkdate($startMonth, $startDay, $startYear)) {
|
||||
$this->error('Invalid start_date.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate end date
|
||||
$endYear = substr($endDate, 0, 4);
|
||||
$endMonth = substr($endDate, 4, 2);
|
||||
$endDay = substr($endDate, 6, 2);
|
||||
|
||||
if (!checkdate($endMonth, $endDay, $endYear)) {
|
||||
$this->error('Invalid end_date.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate date range
|
||||
$startCarbon = Carbon::createFromFormat('Ymd', $startDate);
|
||||
$endCarbon = Carbon::createFromFormat('Ymd', $endDate);
|
||||
|
||||
if ($startCarbon->gt($endCarbon)) {
|
||||
$this->error('Start date cannot be greater than end date.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate range not too long (max 3 months)
|
||||
if ($startCarbon->diffInDays($endCarbon) > 90) {
|
||||
$this->error('Date range cannot exceed 90 days.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate user exists
|
||||
$user = User::find($userId);
|
||||
if (!$user) {
|
||||
$this->error("User with ID {$userId} not found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate date range array from start to end date
|
||||
* Menghasilkan array tanggal dari start sampai end date
|
||||
*
|
||||
* @param string $startDate
|
||||
* @param string $endDate
|
||||
* @return array
|
||||
*/
|
||||
private function generateDateRange(string $startDate, string $endDate): array
|
||||
{
|
||||
$dates = [];
|
||||
$current = Carbon::createFromFormat('Ymd', $startDate);
|
||||
$end = Carbon::createFromFormat('Ymd', $endDate);
|
||||
|
||||
while ($current->lte($end)) {
|
||||
$dates[] = $current->format('Ymd');
|
||||
$current->addDay();
|
||||
}
|
||||
|
||||
Log::info('Generated date range for bulk processing', [
|
||||
'start_date' => $startDate,
|
||||
'end_date' => $endDate,
|
||||
'total_dates' => count($dates)
|
||||
]);
|
||||
|
||||
return $dates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get account list based on options
|
||||
* Mengambil daftar rekening berdasarkan parameter atau menggunakan default
|
||||
*
|
||||
* @param string|null $accountsOption
|
||||
* @param string|null $clientFilter
|
||||
* @return array
|
||||
*/
|
||||
private function getAccountList(?string $accountsOption, ?string $clientFilter): array
|
||||
{
|
||||
// Jika ada parameter accounts, gunakan itu
|
||||
if ($accountsOption) {
|
||||
$accounts = array_map('trim', explode(',', $accountsOption));
|
||||
$accounts = array_filter($accounts); // Remove empty values
|
||||
|
||||
Log::info('Using custom account list from parameter', [
|
||||
'total_accounts' => count($accounts),
|
||||
'accounts' => $accounts
|
||||
]);
|
||||
|
||||
return ['CUSTOM' => $accounts];
|
||||
}
|
||||
|
||||
// Jika tidak ada parameter accounts, gunakan default list
|
||||
$accountList = ['DEFAULT' => $this->defaultAccounts];
|
||||
|
||||
// Filter by client jika ada (untuk backward compatibility)
|
||||
if ($clientFilter) {
|
||||
// Untuk saat ini, client filter tidak digunakan karena kita pakai list baru
|
||||
// Tapi tetap log untuk tracking
|
||||
Log::info('Client filter specified but using default account list', [
|
||||
'client_filter' => $clientFilter
|
||||
]);
|
||||
}
|
||||
|
||||
Log::info('Using default account list', [
|
||||
'total_accounts' => count($this->defaultAccounts)
|
||||
]);
|
||||
|
||||
return $accountList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show summary of accounts and dates to be processed
|
||||
* Menampilkan ringkasan rekening dan tanggal yang akan diproses
|
||||
*
|
||||
* @param array $accountList
|
||||
* @param array $dateRange
|
||||
* @param bool $isDryRun
|
||||
*/
|
||||
private function showSummary(array $accountList, array $dateRange, bool $isDryRun): void
|
||||
{
|
||||
$this->info('\n=== SUMMARY ===');
|
||||
$this->info("Date Range: {$dateRange[0]} to {$dateRange[count($dateRange)-1]} ({" . count($dateRange) . "} days)");
|
||||
$this->info("Mode: " . ($isDryRun ? 'DRY RUN' : 'LIVE'));
|
||||
$this->info('');
|
||||
|
||||
$totalAccounts = 0;
|
||||
foreach ($accountList as $groupName => $accounts) {
|
||||
$accountCount = count($accounts);
|
||||
$totalAccounts += $accountCount;
|
||||
$this->info("Group: {$groupName} ({$accountCount} accounts)");
|
||||
|
||||
// Show first 10 accounts, then summarize if more
|
||||
$displayAccounts = array_slice($accounts, 0, 10);
|
||||
foreach ($displayAccounts as $account) {
|
||||
$this->line(" - {$account}");
|
||||
}
|
||||
|
||||
if (count($accounts) > 10) {
|
||||
$remaining = count($accounts) - 10;
|
||||
$this->line(" ... and {$remaining} more accounts");
|
||||
}
|
||||
}
|
||||
|
||||
$totalJobs = $totalAccounts * count($dateRange);
|
||||
$this->info("\nTotal accounts: {$totalAccounts}");
|
||||
$this->info("Total dates: " . count($dateRange));
|
||||
$this->info("Total jobs to be created: {$totalJobs}");
|
||||
$this->info('===============\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Process all accounts for all dates in range
|
||||
* Memproses semua rekening untuk semua tanggal dalam range
|
||||
*
|
||||
* @param array $accountList
|
||||
* @param array $dateRange
|
||||
* @param int $userId
|
||||
* @return array
|
||||
*/
|
||||
private function processAccountsWithDateRange(array $accountList, array $dateRange, int $userId): array
|
||||
{
|
||||
$results = [
|
||||
'success' => [],
|
||||
'failed' => [],
|
||||
'total' => 0
|
||||
];
|
||||
|
||||
$totalJobs = $this->getTotalAccountCount($accountList) * count($dateRange);
|
||||
|
||||
$this->info('Starting report generation for date range...');
|
||||
$progressBar = $this->output->createProgressBar($totalJobs);
|
||||
$progressBar->start();
|
||||
|
||||
foreach ($dateRange as $period) {
|
||||
foreach ($accountList as $groupName => $accounts) {
|
||||
foreach ($accounts as $accountNumber) {
|
||||
$results['total']++;
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
// Create report log entry
|
||||
$reportLog = $this->createReportLog($accountNumber, $period, $userId, $groupName);
|
||||
|
||||
if (!$reportLog) {
|
||||
throw new Exception('Failed to create report log entry');
|
||||
}
|
||||
|
||||
// Dispatch the job
|
||||
GenerateClosingBalanceReportJob::dispatch($accountNumber, $period, $reportLog->id);
|
||||
|
||||
DB::commit();
|
||||
|
||||
$results['success'][] = [
|
||||
'group' => $groupName,
|
||||
'account' => $accountNumber,
|
||||
'period' => $period,
|
||||
'report_log_id' => $reportLog->id
|
||||
];
|
||||
|
||||
Log::info('Bulk command: Report job dispatched successfully', [
|
||||
'group' => $groupName,
|
||||
'account_number' => $accountNumber,
|
||||
'period' => $period,
|
||||
'report_log_id' => $reportLog->id,
|
||||
'user_id' => $userId
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
DB::rollback();
|
||||
|
||||
$results['failed'][] = [
|
||||
'group' => $groupName,
|
||||
'account' => $accountNumber,
|
||||
'period' => $period,
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
|
||||
Log::error('Bulk command: Error processing account', [
|
||||
'group' => $groupName,
|
||||
'account_number' => $accountNumber,
|
||||
'period' => $period,
|
||||
'user_id' => $userId,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
|
||||
$progressBar->advance();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$progressBar->finish();
|
||||
$this->info('\n');
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create report log entry
|
||||
* Membuat entry log laporan
|
||||
*
|
||||
* @param string $accountNumber
|
||||
* @param string $period
|
||||
* @param int $userId
|
||||
* @param string $groupName
|
||||
* @return ClosingBalanceReportLog|null
|
||||
*/
|
||||
private function createReportLog(string $accountNumber, string $period, int $userId, string $groupName): ?ClosingBalanceReportLog
|
||||
{
|
||||
try {
|
||||
// Convert period string to Carbon date
|
||||
$reportDate = Carbon::createFromFormat('Ymd', $period);
|
||||
|
||||
$reportLog = ClosingBalanceReportLog::create([
|
||||
'account_number' => $accountNumber,
|
||||
'period' => $period,
|
||||
'report_date' => $reportDate,
|
||||
'status' => 'pending',
|
||||
'user_id' => $userId,
|
||||
'created_by' => $userId,
|
||||
'updated_by' => $userId,
|
||||
'ip_address' => request()->ip() ?? '127.0.0.1',
|
||||
'user_agent' => 'Console Command - Bulk Range',
|
||||
'remarks' => "Bulk generation for group: {$groupName}, period: {$period}",
|
||||
'created_at' => now(),
|
||||
'updated_at' => now()
|
||||
]);
|
||||
|
||||
return $reportLog;
|
||||
|
||||
} catch (Exception $e) {
|
||||
Log::error('Bulk command: Error creating report log', [
|
||||
'account_number' => $accountNumber,
|
||||
'period' => $period,
|
||||
'user_id' => $userId,
|
||||
'group_name' => $groupName,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total account count
|
||||
* Menghitung total jumlah rekening
|
||||
*
|
||||
* @param array $accountList
|
||||
* @return int
|
||||
*/
|
||||
private function getTotalAccountCount(array $accountList): int
|
||||
{
|
||||
$total = 0;
|
||||
foreach ($accountList as $accounts) {
|
||||
$total += count($accounts);
|
||||
}
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show processing results
|
||||
* Menampilkan hasil pemrosesan
|
||||
*
|
||||
* @param array $results
|
||||
*/
|
||||
private function showResults(array $results): void
|
||||
{
|
||||
$this->info('\n=== RESULTS ===');
|
||||
$this->info("Total processed: {$results['total']}");
|
||||
$this->info("Successful: " . count($results['success']));
|
||||
$this->info("Failed: " . count($results['failed']));
|
||||
|
||||
if (!empty($results['failed'])) {
|
||||
$this->error('\nFailed jobs:');
|
||||
foreach (array_slice($results['failed'], 0, 10) as $failed) {
|
||||
$this->error(" - {$failed['group']}: {$failed['account']} ({$failed['period']}) - {$failed['error']}");
|
||||
}
|
||||
|
||||
if (count($results['failed']) > 10) {
|
||||
$remaining = count($results['failed']) - 10;
|
||||
$this->error(" ... and {$remaining} more failed jobs");
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($results['success'])) {
|
||||
$this->info('\nSample successful jobs:');
|
||||
foreach (array_slice($results['success'], 0, 5) as $success) {
|
||||
$this->info(" - {$success['group']}: {$success['account']} ({$success['period']}) - Log ID: {$success['report_log_id']}");
|
||||
}
|
||||
|
||||
if (count($results['success']) > 5) {
|
||||
$remaining = count($results['success']) - 5;
|
||||
$this->info(" ... and {$remaining} more successful jobs");
|
||||
}
|
||||
}
|
||||
|
||||
$this->info('\nCheck the closing_balance_report_logs table for progress.');
|
||||
$this->info('===============\n');
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,8 @@ use Modules\Webstatement\Console\{
|
||||
GenerateAtmTransactionReport,
|
||||
GenerateBiayaKartuCsvCommand,
|
||||
AutoSendStatementEmailCommand,
|
||||
GenerateClosingBalanceReportCommand
|
||||
GenerateClosingBalanceReportCommand,
|
||||
GenerateClosingBalanceReportBulkCommand,
|
||||
};
|
||||
use Modules\Webstatement\Jobs\UpdateAtmCardBranchCurrencyJob;
|
||||
|
||||
@@ -79,6 +80,7 @@ class WebstatementServiceProvider extends ServiceProvider
|
||||
UpdateAllAtmCardsCommand::class,
|
||||
AutoSendStatementEmailCommand::class,
|
||||
GenerateClosingBalanceReportCommand::class,
|
||||
GenerateClosingBalanceReportBulkCommand::class,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user