Add membership fee system with disability discount and fix document permissions
Features: - Implement two fee types: entrance fee and annual fee (both NT$1,000) - Add 50% discount for disability certificate holders - Add disability certificate upload in member profile - Integrate disability verification into cashier approval workflow - Add membership fee settings in system admin Document permissions: - Fix hard-coded role logic in Document model - Use permission-based authorization instead of role checks Additional features: - Add announcements, general ledger, and trial balance modules - Add income management and accounting entries - Add comprehensive test suite with factories - Update UI translations to Traditional Chinese 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -38,8 +38,7 @@ class TestDataSeeder extends Seeder
|
||||
|
||||
// Ensure required seeders have run
|
||||
$this->call([
|
||||
RoleSeeder::class,
|
||||
PaymentVerificationRolesSeeder::class,
|
||||
FinancialWorkflowPermissionsSeeder::class,
|
||||
ChartOfAccountSeeder::class,
|
||||
IssueLabelSeeder::class,
|
||||
]);
|
||||
@@ -86,62 +85,68 @@ class TestDataSeeder extends Seeder
|
||||
$users = [];
|
||||
|
||||
// 1. Super Admin
|
||||
$admin = User::create([
|
||||
'name' => 'Admin User',
|
||||
'email' => 'admin@test.com',
|
||||
'password' => Hash::make('password'),
|
||||
'is_admin' => true,
|
||||
]);
|
||||
$admin = User::firstOrCreate(
|
||||
['email' => 'admin@test.com'],
|
||||
[
|
||||
'name' => 'Admin User',
|
||||
'password' => Hash::make('password'),
|
||||
]
|
||||
);
|
||||
$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');
|
||||
// 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. Payment Accountant
|
||||
$accountant = User::create([
|
||||
'name' => 'Accountant User',
|
||||
'email' => 'accountant@test.com',
|
||||
'password' => Hash::make('password'),
|
||||
'is_admin' => true,
|
||||
]);
|
||||
$accountant->assignRole('payment_accountant');
|
||||
// 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. Payment Chair
|
||||
$chair = User::create([
|
||||
'name' => 'Chair User',
|
||||
'email' => 'chair@test.com',
|
||||
'password' => Hash::make('password'),
|
||||
'is_admin' => true,
|
||||
]);
|
||||
$chair->assignRole('payment_chair');
|
||||
// 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::create([
|
||||
'name' => 'Membership Manager',
|
||||
'email' => 'manager@test.com',
|
||||
'password' => Hash::make('password'),
|
||||
'is_admin' => true,
|
||||
]);
|
||||
$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::create([
|
||||
'name' => 'Regular Member',
|
||||
'email' => 'member@test.com',
|
||||
'password' => Hash::make('password'),
|
||||
'is_admin' => false,
|
||||
]);
|
||||
$member = User::firstOrCreate(
|
||||
['email' => 'member@test.com'],
|
||||
[
|
||||
'name' => 'Regular Member',
|
||||
'password' => Hash::make('password'),
|
||||
]
|
||||
);
|
||||
$users['member'] = $member;
|
||||
|
||||
return $users;
|
||||
@@ -158,97 +163,107 @@ class TestDataSeeder extends Seeder
|
||||
|
||||
// 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)),
|
||||
]);
|
||||
$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::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)),
|
||||
]);
|
||||
$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::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)),
|
||||
]);
|
||||
$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::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)),
|
||||
]);
|
||||
$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::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,
|
||||
]);
|
||||
$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++;
|
||||
}
|
||||
|
||||
@@ -264,38 +279,41 @@ class TestDataSeeder extends Seeder
|
||||
$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' => '待審核的繳費記錄',
|
||||
]);
|
||||
$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::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' => '已通過出納審核',
|
||||
]);
|
||||
$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
|
||||
@@ -303,22 +321,24 @@ class TestDataSeeder extends Seeder
|
||||
$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' => '已通過會計審核',
|
||||
]);
|
||||
$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)
|
||||
@@ -327,42 +347,46 @@ class TestDataSeeder extends Seeder
|
||||
$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' => '已完成三階段審核',
|
||||
]);
|
||||
$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::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' => '已退回',
|
||||
]);
|
||||
$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;
|
||||
@@ -747,12 +771,12 @@ class TestDataSeeder extends Seeder
|
||||
$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'],
|
||||
['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('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
|
||||
Reference in New Issue
Block a user