diff --git a/app/Console/GenerateClosingBalanceReportBulkCommand.php b/app/Console/GenerateClosingBalanceReportBulkCommand.php new file mode 100644 index 0000000..656bc6f --- /dev/null +++ b/app/Console/GenerateClosingBalanceReportBulkCommand.php @@ -0,0 +1,532 @@ +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'); + } +} diff --git a/app/Providers/WebstatementServiceProvider.php b/app/Providers/WebstatementServiceProvider.php index 5085f4f..7c7a464 100644 --- a/app/Providers/WebstatementServiceProvider.php +++ b/app/Providers/WebstatementServiceProvider.php @@ -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, ]); }