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, 'QRIS' => $this->qrisAccount]; // 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, $groupName); 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'); } }