command->info('🌱 Starting Test Data Seeding...'); // Ensure required seeders have run $this->call([ FinancialWorkflowPermissionsSeeder::class, ChartOfAccountSeeder::class, IssueLabelSeeder::class, ]); // Create test users with different roles $users = $this->createTestUsers(); $this->command->info('✅ Created 6 test users with different roles'); // Create members in various states $members = $this->createTestMembers($users); $this->command->info('✅ Created 20 members in various membership states'); // Create payments at different approval stages $payments = $this->createTestPayments($members, $users); $this->command->info('✅ Created 30 membership payments at different approval stages'); // Create issues with various statuses $issues = $this->createTestIssues($users, $members); $this->command->info('✅ Created 15 issues with various statuses and relationships'); // Create budgets with items $budgets = $this->createTestBudgets($users); $this->command->info('✅ Created 5 budgets with budget items in different states'); // Create finance documents $financeDocuments = $this->createTestFinanceDocuments($users); $this->command->info('✅ Created 10 finance documents'); // Create sample transactions $transactions = $this->createTestTransactions($users); $this->command->info('✅ Created sample transactions'); $this->command->info(''); $this->command->info('🎉 Test Data Seeding Complete!'); $this->command->info(''); $this->displayTestAccounts($users); } /** * Create test users with different roles */ private function createTestUsers(): array { $users = []; // 1. Super Admin $admin = User::firstOrCreate( ['email' => 'admin@test.com'], [ 'name' => 'Admin User', 'password' => Hash::make('password'), ] ); $admin->assignRole('admin'); $users['admin'] = $admin; // 2. Finance Cashier (整合原 payment_cashier) $cashier = User::firstOrCreate( ['email' => 'cashier@test.com'], [ 'name' => 'Cashier User', 'password' => Hash::make('password'), ] ); $cashier->assignRole('finance_cashier'); $users['cashier'] = $cashier; // 3. Finance Accountant (整合原 payment_accountant) $accountant = User::firstOrCreate( ['email' => 'accountant@test.com'], [ 'name' => 'Accountant User', 'password' => Hash::make('password'), ] ); $accountant->assignRole('finance_accountant'); $users['accountant'] = $accountant; // 4. Finance Chair (整合原 payment_chair) $chair = User::firstOrCreate( ['email' => 'chair@test.com'], [ 'name' => 'Chair User', 'password' => Hash::make('password'), ] ); $chair->assignRole('finance_chair'); $users['chair'] = $chair; // 5. Membership Manager $manager = User::firstOrCreate( ['email' => 'manager@test.com'], [ 'name' => 'Membership Manager', 'password' => Hash::make('password'), ] ); $manager->assignRole('membership_manager'); $users['manager'] = $manager; // 6. Regular Member User $member = User::firstOrCreate( ['email' => 'member@test.com'], [ 'name' => 'Regular Member', 'password' => Hash::make('password'), ] ); $users['member'] = $member; return $users; } /** * Create test members in various states */ private function createTestMembers(array $users): array { $members = []; $taiwanCities = ['台北市', '新北市', '台中市', '台南市', '高雄市', '桃園市']; $counter = 1; // 5 Pending Members for ($i = 0; $i < 5; $i++) { $members[] = Member::firstOrCreate( ['email' => "pending{$counter}@test.com"], [ 'user_id' => $i === 0 ? $users['member']->id : null, 'full_name' => "待審核會員 {$counter}", 'phone' => '09' . str_pad($counter, 8, '0', STR_PAD_LEFT), 'address_line_1' => "測試地址 {$counter} 號", 'city' => $taiwanCities[array_rand($taiwanCities)], 'postal_code' => '100', 'membership_status' => Member::STATUS_PENDING, 'membership_type' => Member::TYPE_REGULAR, 'national_id_encrypted' => Crypt::encryptString('A' . str_pad($counter, 9, '0', STR_PAD_LEFT)), 'national_id_hash' => hash('sha256', 'A' . str_pad($counter, 9, '0', STR_PAD_LEFT)), ] ); $counter++; } // 8 Active Members for ($i = 0; $i < 8; $i++) { $startDate = now()->subMonths(rand(1, 6)); $members[] = Member::firstOrCreate( ['email' => "active{$counter}@test.com"], [ 'user_id' => null, 'full_name' => "活躍會員 {$counter}", 'phone' => '09' . str_pad($counter, 8, '0', STR_PAD_LEFT), 'address_line_1' => "測試地址 {$counter} 號", 'city' => $taiwanCities[array_rand($taiwanCities)], 'postal_code' => '100', 'membership_status' => Member::STATUS_ACTIVE, 'membership_type' => $i < 6 ? Member::TYPE_REGULAR : ($i === 6 ? Member::TYPE_HONORARY : Member::TYPE_STUDENT), 'membership_started_at' => $startDate, 'membership_expires_at' => $startDate->copy()->addYear(), 'emergency_contact_name' => "緊急聯絡人 {$counter}", 'emergency_contact_phone' => '02-12345678', 'national_id_encrypted' => Crypt::encryptString('A' . str_pad($counter, 9, '0', STR_PAD_LEFT)), 'national_id_hash' => hash('sha256', 'A' . str_pad($counter, 9, '0', STR_PAD_LEFT)), ] ); $counter++; } // 3 Expired Members for ($i = 0; $i < 3; $i++) { $startDate = now()->subYears(2); $members[] = Member::firstOrCreate( ['email' => "expired{$counter}@test.com"], [ 'user_id' => null, 'full_name' => "過期會員 {$counter}", 'phone' => '09' . str_pad($counter, 8, '0', STR_PAD_LEFT), 'address_line_1' => "測試地址 {$counter} 號", 'city' => $taiwanCities[array_rand($taiwanCities)], 'postal_code' => '100', 'membership_status' => Member::STATUS_EXPIRED, 'membership_type' => Member::TYPE_REGULAR, 'membership_started_at' => $startDate, 'membership_expires_at' => $startDate->copy()->addYear(), 'national_id_encrypted' => Crypt::encryptString('A' . str_pad($counter, 9, '0', STR_PAD_LEFT)), 'national_id_hash' => hash('sha256', 'A' . str_pad($counter, 9, '0', STR_PAD_LEFT)), ] ); $counter++; } // 2 Suspended Members for ($i = 0; $i < 2; $i++) { $members[] = Member::firstOrCreate( ['email' => "suspended{$counter}@test.com"], [ 'user_id' => null, 'full_name' => "停權會員 {$counter}", 'phone' => '09' . str_pad($counter, 8, '0', STR_PAD_LEFT), 'address_line_1' => "測試地址 {$counter} 號", 'city' => $taiwanCities[array_rand($taiwanCities)], 'postal_code' => '100', 'membership_status' => Member::STATUS_SUSPENDED, 'membership_type' => Member::TYPE_REGULAR, 'national_id_encrypted' => Crypt::encryptString('A' . str_pad($counter, 9, '0', STR_PAD_LEFT)), 'national_id_hash' => hash('sha256', 'A' . str_pad($counter, 9, '0', STR_PAD_LEFT)), ] ); $counter++; } // 2 Additional Pending Members (total 20) for ($i = 0; $i < 2; $i++) { $members[] = Member::firstOrCreate( ['email' => "newmember{$counter}@test.com"], [ 'user_id' => null, 'full_name' => "新申請會員 {$counter}", 'phone' => '09' . str_pad($counter, 8, '0', STR_PAD_LEFT), 'address_line_1' => "測試地址 {$counter} 號", 'city' => $taiwanCities[array_rand($taiwanCities)], 'postal_code' => '100', 'membership_status' => Member::STATUS_PENDING, 'membership_type' => Member::TYPE_REGULAR, ] ); $counter++; } return $members; } /** * Create test membership payments at different approval stages */ private function createTestPayments(array $members, array $users): array { $payments = []; $paymentMethods = [ MembershipPayment::METHOD_BANK_TRANSFER, MembershipPayment::METHOD_CASH, ]; // 10 Pending Payments for ($i = 0; $i < 10; $i++) { $payments[] = MembershipPayment::firstOrCreate( ['reference' => 'REF-' . str_pad($i + 1, 5, '0', STR_PAD_LEFT)], [ 'member_id' => $members[$i]->id, 'amount' => 1000, 'paid_at' => now()->subDays(rand(1, 10)), 'payment_method' => $paymentMethods[array_rand($paymentMethods)], 'receipt_path' => 'receipts/test-receipt-' . ($i + 1) . '.pdf', 'status' => MembershipPayment::STATUS_PENDING, 'notes' => '待審核的繳費記錄', ] ); } // 8 Approved by Cashier for ($i = 10; $i < 18; $i++) { $payments[] = MembershipPayment::firstOrCreate( ['reference' => 'REF-' . str_pad($i + 1, 5, '0', STR_PAD_LEFT)], [ 'member_id' => $members[$i % count($members)]->id, 'amount' => 1000, 'paid_at' => now()->subDays(rand(5, 15)), 'payment_method' => $paymentMethods[array_rand($paymentMethods)], 'receipt_path' => 'receipts/test-receipt-' . ($i + 1) . '.pdf', 'status' => MembershipPayment::STATUS_APPROVED_CASHIER, 'verified_by_cashier_id' => $users['cashier']->id, 'cashier_verified_at' => now()->subDays(rand(3, 12)), 'cashier_notes' => '收據已核對,金額無誤', 'notes' => '已通過出納審核', ] ); } // 6 Approved by Accountant for ($i = 18; $i < 24; $i++) { $cashierVerifiedAt = now()->subDays(rand(10, 20)); $accountantVerifiedAt = $cashierVerifiedAt->copy()->addDays(rand(1, 3)); $payments[] = MembershipPayment::firstOrCreate( ['reference' => 'REF-' . str_pad($i + 1, 5, '0', STR_PAD_LEFT)], [ 'member_id' => $members[$i % count($members)]->id, 'amount' => 1000, 'paid_at' => now()->subDays(rand(15, 25)), 'payment_method' => $paymentMethods[array_rand($paymentMethods)], 'receipt_path' => 'receipts/test-receipt-' . ($i + 1) . '.pdf', 'status' => MembershipPayment::STATUS_APPROVED_ACCOUNTANT, 'verified_by_cashier_id' => $users['cashier']->id, 'cashier_verified_at' => $cashierVerifiedAt, 'cashier_notes' => '收據已核對,金額無誤', 'verified_by_accountant_id' => $users['accountant']->id, 'accountant_verified_at' => $accountantVerifiedAt, 'accountant_notes' => '帳務核對完成', 'notes' => '已通過會計審核', ] ); } // 4 Fully Approved (Chair approved - member activated) for ($i = 24; $i < 28; $i++) { $cashierVerifiedAt = now()->subDays(rand(20, 30)); $accountantVerifiedAt = $cashierVerifiedAt->copy()->addDays(rand(1, 3)); $chairVerifiedAt = $accountantVerifiedAt->copy()->addDays(rand(1, 2)); $payments[] = MembershipPayment::firstOrCreate( ['reference' => 'REF-' . str_pad($i + 1, 5, '0', STR_PAD_LEFT)], [ 'member_id' => $members[$i % count($members)]->id, 'amount' => 1000, 'paid_at' => now()->subDays(rand(25, 35)), 'payment_method' => $paymentMethods[array_rand($paymentMethods)], 'receipt_path' => 'receipts/test-receipt-' . ($i + 1) . '.pdf', 'status' => MembershipPayment::STATUS_APPROVED_CHAIR, 'verified_by_cashier_id' => $users['cashier']->id, 'cashier_verified_at' => $cashierVerifiedAt, 'cashier_notes' => '收據已核對,金額無誤', 'verified_by_accountant_id' => $users['accountant']->id, 'accountant_verified_at' => $accountantVerifiedAt, 'accountant_notes' => '帳務核對完成', 'verified_by_chair_id' => $users['chair']->id, 'chair_verified_at' => $chairVerifiedAt, 'chair_notes' => '最終批准', 'notes' => '已完成三階段審核', ] ); } // 2 Rejected Payments for ($i = 28; $i < 30; $i++) { $payments[] = MembershipPayment::firstOrCreate( ['reference' => 'REF-' . str_pad($i + 1, 5, '0', STR_PAD_LEFT)], [ 'member_id' => $members[$i % count($members)]->id, 'amount' => 1000, 'paid_at' => now()->subDays(rand(5, 10)), 'payment_method' => $paymentMethods[array_rand($paymentMethods)], 'receipt_path' => 'receipts/test-receipt-' . ($i + 1) . '.pdf', 'status' => MembershipPayment::STATUS_REJECTED, 'rejected_by_user_id' => $users['cashier']->id, 'rejected_at' => now()->subDays(rand(3, 8)), 'rejection_reason' => $i === 28 ? '收據影像不清晰,無法辨識' : '金額與收據不符', 'notes' => '已退回', ] ); } return $payments; } /** * Create test issues with various statuses */ private function createTestIssues(array $users, array $members): array { $issues = []; $labels = IssueLabel::all(); // 5 New/Open Issues for ($i = 0; $i < 5; $i++) { $issue = Issue::create([ 'title' => "新任務:測試項目 " . ($i + 1), 'description' => "這是一個新的測試任務,用於系統測試。\n\n## 任務說明\n- 測試項目 A\n- 測試項目 B\n- 測試項目 C", 'issue_type' => [Issue::TYPE_WORK_ITEM, Issue::TYPE_PROJECT_TASK][array_rand([0, 1])], 'status' => Issue::STATUS_NEW, 'priority' => [Issue::PRIORITY_LOW, Issue::PRIORITY_MEDIUM, Issue::PRIORITY_HIGH][array_rand([0, 1, 2])], 'created_by_user_id' => $users['admin']->id, 'due_date' => now()->addDays(rand(7, 30)), 'estimated_hours' => rand(2, 16), ]); // Add labels if ($labels->count() > 0) { $issue->labels()->attach($labels->random(rand(1, min(3, $labels->count())))); } $issues[] = $issue; } // 4 In Progress Issues for ($i = 5; $i < 9; $i++) { $issue = Issue::create([ 'title' => "進行中:開發任務 " . ($i + 1), 'description' => "這是一個進行中的開發任務。\n\n## 進度\n- [x] 需求分析\n- [x] 設計\n- [ ] 實作\n- [ ] 測試", 'issue_type' => Issue::TYPE_WORK_ITEM, 'status' => Issue::STATUS_IN_PROGRESS, 'priority' => [Issue::PRIORITY_MEDIUM, Issue::PRIORITY_HIGH][array_rand([0, 1])], 'created_by_user_id' => $users['admin']->id, 'assigned_to_user_id' => $users['member']->id, 'due_date' => now()->addDays(rand(3, 14)), 'estimated_hours' => rand(4, 20), ]); // Add time logs IssueTimeLog::create([ 'issue_id' => $issue->id, 'user_id' => $users['member']->id, 'hours' => rand(1, 5), 'description' => '開發進度更新', 'logged_at' => now()->subDays(rand(1, 3)), ]); // Add comments IssueComment::create([ 'issue_id' => $issue->id, 'user_id' => $users['admin']->id, 'comment' => '請加快進度,謝謝!', 'created_at' => now()->subDays(rand(1, 2)), ]); if ($labels->count() > 0) { $issue->labels()->attach($labels->random(rand(1, min(2, $labels->count())))); } $issues[] = $issue; } // 3 Resolved Issues (in review) for ($i = 9; $i < 12; $i++) { $issue = Issue::create([ 'title' => "已完成:維護項目 " . ($i + 1), 'description' => "維護任務已完成,等待審核。", 'issue_type' => Issue::TYPE_MAINTENANCE, 'status' => Issue::STATUS_REVIEW, 'priority' => Issue::PRIORITY_MEDIUM, 'created_by_user_id' => $users['admin']->id, 'assigned_to_user_id' => $users['member']->id, 'reviewer_id' => $users['manager']->id, 'due_date' => now()->subDays(rand(1, 5)), 'estimated_hours' => rand(2, 8), ]); // Add completed time logs IssueTimeLog::create([ 'issue_id' => $issue->id, 'user_id' => $users['member']->id, 'hours' => rand(2, 6), 'description' => '任務完成', 'logged_at' => now()->subDays(rand(1, 3)), ]); IssueComment::create([ 'issue_id' => $issue->id, 'user_id' => $users['member']->id, 'comment' => '任務已完成,請審核。', 'created_at' => now()->subDays(1), ]); if ($labels->count() > 0) { $issue->labels()->attach($labels->random(1)); } $issues[] = $issue; } // 2 Closed Issues for ($i = 12; $i < 14; $i++) { $closedAt = now()->subDays(rand(7, 30)); $issue = Issue::create([ 'title' => "已結案:專案 " . ($i + 1), 'description' => "專案已完成並結案。", 'issue_type' => Issue::TYPE_PROJECT_TASK, 'status' => Issue::STATUS_CLOSED, 'priority' => Issue::PRIORITY_HIGH, 'created_by_user_id' => $users['admin']->id, 'assigned_to_user_id' => $users['member']->id, 'due_date' => $closedAt->copy()->subDays(rand(1, 5)), 'closed_at' => $closedAt, 'estimated_hours' => rand(8, 24), ]); IssueTimeLog::create([ 'issue_id' => $issue->id, 'user_id' => $users['member']->id, 'hours' => rand(8, 20), 'description' => '專案完成', 'logged_at' => $closedAt->copy()->subDays(1), ]); IssueComment::create([ 'issue_id' => $issue->id, 'user_id' => $users['admin']->id, 'comment' => '專案驗收通過,結案。', 'created_at' => $closedAt, ]); if ($labels->count() > 0) { $issue->labels()->attach($labels->random(rand(1, min(2, $labels->count())))); } $issues[] = $issue; } // 1 Member Request $issue = Issue::create([ 'title' => "會員需求:更新會員資料", 'description' => "會員要求更新個人資料。", 'issue_type' => Issue::TYPE_MEMBER_REQUEST, 'status' => Issue::STATUS_ASSIGNED, 'priority' => Issue::PRIORITY_MEDIUM, 'created_by_user_id' => $users['admin']->id, 'assigned_to_user_id' => $users['manager']->id, 'member_id' => $members[0]->id, 'due_date' => now()->addDays(3), 'estimated_hours' => 1, ]); if ($labels->count() > 0) { $issue->labels()->attach($labels->random(1)); } $issues[] = $issue; return $issues; } /** * Create test budgets with items */ private function createTestBudgets(array $users): array { $budgets = []; $accounts = ChartOfAccount::all(); if ($accounts->isEmpty()) { $this->command->warn('⚠️ No Chart of Accounts found. Skipping budget creation.'); return $budgets; } $incomeAccounts = $accounts->where('account_type', ChartOfAccount::TYPE_INCOME); $expenseAccounts = $accounts->where('account_type', ChartOfAccount::TYPE_EXPENSE); // 1. Draft Budget $budget = Budget::create([ 'name' => '2025年度預算草案', 'fiscal_year' => 2025, 'status' => Budget::STATUS_DRAFT, 'created_by_user_id' => $users['admin']->id, 'notes' => '年度預算初稿,待提交', ]); $this->createBudgetItems($budget, $incomeAccounts, $expenseAccounts, 50000, 40000); $budgets[] = $budget; // 2. Submitted Budget $budget = Budget::create([ 'name' => '2024下半年預算', 'fiscal_year' => 2024, 'status' => Budget::STATUS_SUBMITTED, 'created_by_user_id' => $users['admin']->id, 'submitted_at' => now()->subDays(5), 'notes' => '已提交,等待審核', ]); $this->createBudgetItems($budget, $incomeAccounts, $expenseAccounts, 60000, 50000); $budgets[] = $budget; // 3. Approved Budget $budget = Budget::create([ 'name' => '2024上半年預算', 'fiscal_year' => 2024, 'status' => Budget::STATUS_APPROVED, 'created_by_user_id' => $users['admin']->id, 'submitted_at' => now()->subDays(60), 'approved_by_user_id' => $users['chair']->id, 'approved_at' => now()->subDays(55), 'notes' => '已核准', ]); $this->createBudgetItems($budget, $incomeAccounts, $expenseAccounts, 70000, 60000); $budgets[] = $budget; // 4. Active Budget $budget = Budget::create([ 'name' => '2024年度預算', 'fiscal_year' => 2024, 'status' => Budget::STATUS_ACTIVE, 'created_by_user_id' => $users['admin']->id, 'submitted_at' => now()->subMonths(11), 'approved_by_user_id' => $users['chair']->id, 'approved_at' => now()->subMonths(10), 'activated_at' => now()->subMonths(10), 'notes' => '執行中的年度預算', ]); $this->createBudgetItems($budget, $incomeAccounts, $expenseAccounts, 100000, 80000, true); $budgets[] = $budget; // 5. Closed Budget $budget = Budget::create([ 'name' => '2023年度預算', 'fiscal_year' => 2023, 'status' => Budget::STATUS_CLOSED, 'created_by_user_id' => $users['admin']->id, 'submitted_at' => now()->subMonths(23), 'approved_by_user_id' => $users['chair']->id, 'approved_at' => now()->subMonths(22), 'activated_at' => now()->subMonths(22), 'closed_at' => now()->subMonths(10), 'notes' => '已結案的年度預算', ]); $this->createBudgetItems($budget, $incomeAccounts, $expenseAccounts, 90000, 75000, true); $budgets[] = $budget; return $budgets; } /** * Create budget items for a budget */ private function createBudgetItems( Budget $budget, $incomeAccounts, $expenseAccounts, int $totalIncome, int $totalExpense, bool $withActuals = false ): void { // Create income items if ($incomeAccounts->count() > 0) { $itemCount = min(3, $incomeAccounts->count()); $accounts = $incomeAccounts->random($itemCount); foreach ($accounts as $index => $account) { $budgetedAmount = (int)($totalIncome / $itemCount); $actualAmount = $withActuals ? (int)($budgetedAmount * rand(80, 120) / 100) : 0; BudgetItem::create([ 'budget_id' => $budget->id, 'chart_of_account_id' => $account->id, 'budgeted_amount' => $budgetedAmount, 'actual_amount' => $actualAmount, 'notes' => '預算項目', ]); } } // Create expense items if ($expenseAccounts->count() > 0) { $itemCount = min(5, $expenseAccounts->count()); $accounts = $expenseAccounts->random($itemCount); foreach ($accounts as $index => $account) { $budgetedAmount = (int)($totalExpense / $itemCount); $actualAmount = $withActuals ? (int)($budgetedAmount * rand(70, 110) / 100) : 0; BudgetItem::create([ 'budget_id' => $budget->id, 'chart_of_account_id' => $account->id, 'budgeted_amount' => $budgetedAmount, 'actual_amount' => $actualAmount, 'notes' => '支出預算項目', ]); } } } /** * Create test finance documents */ private function createTestFinanceDocuments(array $users): array { $documents = []; $documentTypes = ['invoice', 'receipt', 'contract', 'report']; $statuses = ['pending', 'approved', 'rejected']; for ($i = 1; $i <= 10; $i++) { $documents[] = FinanceDocument::create([ 'document_number' => 'FIN-2024-' . str_pad($i, 4, '0', STR_PAD_LEFT), 'title' => "財務文件 {$i}", 'document_type' => $documentTypes[array_rand($documentTypes)], 'amount' => rand(1000, 50000), 'document_date' => now()->subDays(rand(1, 90)), 'status' => $statuses[array_rand($statuses)], 'uploaded_by_user_id' => $users['admin']->id, 'file_path' => "finance-documents/test-doc-{$i}.pdf", 'notes' => '測試財務文件', ]); } return $documents; } /** * Create sample transactions */ private function createTestTransactions(array $users): array { $transactions = []; $accounts = ChartOfAccount::all(); if ($accounts->isEmpty()) { $this->command->warn('⚠️ No Chart of Accounts found. Skipping transaction creation.'); return $transactions; } // Create 20 sample transactions for ($i = 1; $i <= 20; $i++) { $account = $accounts->random(); $isDebit = $account->account_type === ChartOfAccount::TYPE_EXPENSE || $account->account_type === ChartOfAccount::TYPE_ASSET; $transactions[] = Transaction::create([ 'transaction_date' => now()->subDays(rand(1, 60)), 'chart_of_account_id' => $account->id, 'description' => "測試交易 {$i}:" . $account->account_name, 'debit_amount' => $isDebit ? rand(500, 10000) : 0, 'credit_amount' => !$isDebit ? rand(500, 10000) : 0, 'created_by_user_id' => $users['accountant']->id, 'reference' => 'TXN-' . str_pad($i, 5, '0', STR_PAD_LEFT), 'notes' => '系統測試交易', ]); } return $transactions; } /** * Display test account information */ private function displayTestAccounts(array $users): void { $this->command->info('📋 Test User Accounts:'); $this->command->info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); $this->command->table( ['Role', 'Email', 'Password', 'Permissions'], [ ['Admin (admin)', 'admin@test.com', 'password', 'All permissions'], ['Finance Cashier (finance_cashier)', 'cashier@test.com', 'password', 'Payment + Finance cashier'], ['Finance Accountant (finance_accountant)', 'accountant@test.com', 'password', 'Payment + Finance accountant'], ['Finance Chair (finance_chair)', 'chair@test.com', 'password', 'Payment + Finance chair'], ['Membership Manager (membership_manager)', 'manager@test.com', 'password', 'Membership activation'], ['Member (no role)', 'member@test.com', 'password', 'Member dashboard access'], ] ); $this->command->info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); $this->command->info(''); $this->command->info('🎯 Test Data Summary:'); $this->command->info(' • 20 Members (5 pending, 8 active, 3 expired, 2 suspended, 2 new)'); $this->command->info(' • 30 Payments (10 pending, 8 cashier-approved, 6 accountant-approved, 4 fully-approved, 2 rejected)'); $this->command->info(' • 15 Issues (5 new, 4 in-progress, 3 in-review, 2 closed, 1 member-request)'); $this->command->info(' • 5 Budgets (draft, submitted, approved, active, closed)'); $this->command->info(' • 10 Finance Documents'); $this->command->info(' • 20 Sample Transactions'); $this->command->info(''); } }