Initial commit

This commit is contained in:
2025-11-20 23:21:05 +08:00
commit 13bc6db529
378 changed files with 54527 additions and 0 deletions

View File

@@ -0,0 +1,769 @@
<?php
namespace Database\Seeders;
use App\Models\Budget;
use App\Models\BudgetItem;
use App\Models\ChartOfAccount;
use App\Models\FinanceDocument;
use App\Models\Issue;
use App\Models\IssueComment;
use App\Models\IssueLabel;
use App\Models\IssueTimeLog;
use App\Models\Member;
use App\Models\MembershipPayment;
use App\Models\Transaction;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Hash;
class TestDataSeeder extends Seeder
{
/**
* Run the database seeds.
*
* Creates comprehensive test data for manual and automated testing:
* - 5 test users with different roles
* - 20 members in various states (pending, active, expired, suspended)
* - 30 payments at different approval stages
* - 15 issues with various statuses
* - 5 budgets with items
* - 10 finance documents
* - Sample transactions
*/
public function run(): void
{
$this->command->info('🌱 Starting Test Data Seeding...');
// Ensure required seeders have run
$this->call([
RoleSeeder::class,
PaymentVerificationRolesSeeder::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::create([
'name' => 'Admin User',
'email' => 'admin@test.com',
'password' => Hash::make('password'),
'is_admin' => true,
]);
$admin->assignRole('admin');
$users['admin'] = $admin;
// 2. Payment Cashier
$cashier = User::create([
'name' => 'Cashier User',
'email' => 'cashier@test.com',
'password' => Hash::make('password'),
'is_admin' => true,
]);
$cashier->assignRole('payment_cashier');
$users['cashier'] = $cashier;
// 3. Payment Accountant
$accountant = User::create([
'name' => 'Accountant User',
'email' => 'accountant@test.com',
'password' => Hash::make('password'),
'is_admin' => true,
]);
$accountant->assignRole('payment_accountant');
$users['accountant'] = $accountant;
// 4. Payment Chair
$chair = User::create([
'name' => 'Chair User',
'email' => 'chair@test.com',
'password' => Hash::make('password'),
'is_admin' => true,
]);
$chair->assignRole('payment_chair');
$users['chair'] = $chair;
// 5. Membership Manager
$manager = User::create([
'name' => 'Membership Manager',
'email' => 'manager@test.com',
'password' => Hash::make('password'),
'is_admin' => true,
]);
$manager->assignRole('membership_manager');
$users['manager'] = $manager;
// 6. Regular Member User
$member = User::create([
'name' => 'Regular Member',
'email' => 'member@test.com',
'password' => Hash::make('password'),
'is_admin' => false,
]);
$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::create([
'user_id' => $i === 0 ? $users['member']->id : null,
'full_name' => "待審核會員 {$counter}",
'email' => "pending{$counter}@test.com",
'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::create([
'user_id' => null,
'full_name' => "活躍會員 {$counter}",
'email' => "active{$counter}@test.com",
'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::create([
'user_id' => null,
'full_name' => "過期會員 {$counter}",
'email' => "expired{$counter}@test.com",
'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::create([
'user_id' => null,
'full_name' => "停權會員 {$counter}",
'email' => "suspended{$counter}@test.com",
'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::create([
'user_id' => null,
'full_name' => "新申請會員 {$counter}",
'email' => "newmember{$counter}@test.com",
'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,
MembershipPayment::METHOD_CHECK,
];
// 10 Pending Payments
for ($i = 0; $i < 10; $i++) {
$payments[] = MembershipPayment::create([
'member_id' => $members[$i]->id,
'amount' => 1000,
'paid_at' => now()->subDays(rand(1, 10)),
'payment_method' => $paymentMethods[array_rand($paymentMethods)],
'reference' => 'REF-' . str_pad($i + 1, 5, '0', STR_PAD_LEFT),
'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::create([
'member_id' => $members[$i % count($members)]->id,
'amount' => 1000,
'paid_at' => now()->subDays(rand(5, 15)),
'payment_method' => $paymentMethods[array_rand($paymentMethods)],
'reference' => 'REF-' . str_pad($i + 1, 5, '0', STR_PAD_LEFT),
'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::create([
'member_id' => $members[$i % count($members)]->id,
'amount' => 1000,
'paid_at' => now()->subDays(rand(15, 25)),
'payment_method' => $paymentMethods[array_rand($paymentMethods)],
'reference' => 'REF-' . str_pad($i + 1, 5, '0', STR_PAD_LEFT),
'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::create([
'member_id' => $members[$i % count($members)]->id,
'amount' => 1000,
'paid_at' => now()->subDays(rand(25, 35)),
'payment_method' => $paymentMethods[array_rand($paymentMethods)],
'reference' => 'REF-' . str_pad($i + 1, 5, '0', STR_PAD_LEFT),
'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::create([
'member_id' => $members[$i % count($members)]->id,
'amount' => 1000,
'paid_at' => now()->subDays(rand(5, 10)),
'payment_method' => $paymentMethods[array_rand($paymentMethods)],
'reference' => 'REF-' . str_pad($i + 1, 5, '0', STR_PAD_LEFT),
'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@test.com', 'password', 'All permissions'],
['Cashier', 'cashier@test.com', 'password', 'Tier 1 payment verification'],
['Accountant', 'accountant@test.com', 'password', 'Tier 2 payment verification'],
['Chair', 'chair@test.com', 'password', 'Tier 3 payment verification'],
['Manager', 'manager@test.com', 'password', 'Membership activation'],
['Member', '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('');
}
}