Initial commit
This commit is contained in:
92
database/seeders/AdvancedPermissionsSeeder.php
Normal file
92
database/seeders/AdvancedPermissionsSeeder.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class AdvancedPermissionsSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// Create advanced document feature permissions
|
||||
$permissions = [
|
||||
[
|
||||
'name' => 'manage_system_settings',
|
||||
'description' => 'Access and modify system settings pages'
|
||||
],
|
||||
[
|
||||
'name' => 'use_bulk_import',
|
||||
'description' => 'Use document bulk import feature'
|
||||
],
|
||||
[
|
||||
'name' => 'use_qr_codes',
|
||||
'description' => 'Generate QR codes for documents'
|
||||
],
|
||||
[
|
||||
'name' => 'view_document_statistics',
|
||||
'description' => 'Access document statistics dashboard'
|
||||
],
|
||||
[
|
||||
'name' => 'manage_document_tags',
|
||||
'description' => 'Create, edit, and delete document tags'
|
||||
],
|
||||
[
|
||||
'name' => 'manage_document_expiration',
|
||||
'description' => 'Set expiration dates and configure auto-archive rules'
|
||||
],
|
||||
[
|
||||
'name' => 'export_documents',
|
||||
'description' => 'Export document lists and reports'
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($permissions as $permissionData) {
|
||||
Permission::firstOrCreate(
|
||||
['name' => $permissionData['name']],
|
||||
['guard_name' => 'web']
|
||||
);
|
||||
}
|
||||
|
||||
// Assign all advanced permissions to 'admin' role
|
||||
$adminRole = Role::where('name', 'admin')->first();
|
||||
|
||||
if ($adminRole) {
|
||||
foreach ($permissions as $permissionData) {
|
||||
$permission = Permission::where('name', $permissionData['name'])->first();
|
||||
if ($permission && !$adminRole->hasPermissionTo($permission)) {
|
||||
$adminRole->givePermissionTo($permission);
|
||||
}
|
||||
}
|
||||
|
||||
$this->command->info('Advanced permissions assigned to admin role');
|
||||
}
|
||||
|
||||
// Optionally assign some permissions to 'staff' role
|
||||
$staffRole = Role::where('name', 'staff')->first();
|
||||
|
||||
if ($staffRole) {
|
||||
$staffPermissions = [
|
||||
'use_qr_codes',
|
||||
'view_document_statistics',
|
||||
'export_documents',
|
||||
];
|
||||
|
||||
foreach ($staffPermissions as $permissionName) {
|
||||
$permission = Permission::where('name', $permissionName)->first();
|
||||
if ($permission && !$staffRole->hasPermissionTo($permission)) {
|
||||
$staffRole->givePermissionTo($permission);
|
||||
}
|
||||
}
|
||||
|
||||
$this->command->info('Selected permissions assigned to staff role');
|
||||
}
|
||||
|
||||
$this->command->info('Advanced permissions seeded successfully');
|
||||
}
|
||||
}
|
||||
490
database/seeders/ChartOfAccountSeeder.php
Normal file
490
database/seeders/ChartOfAccountSeeder.php
Normal file
@@ -0,0 +1,490 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\ChartOfAccount;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class ChartOfAccountSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* Taiwan nonprofit standard chart of accounts (會計科目表)
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$accounts = [
|
||||
// Assets (資產) - 1xxx
|
||||
[
|
||||
'account_code' => '1101',
|
||||
'account_name_zh' => '現金',
|
||||
'account_name_en' => 'Cash',
|
||||
'account_type' => 'asset',
|
||||
'category' => '流動資產',
|
||||
'display_order' => 10,
|
||||
],
|
||||
[
|
||||
'account_code' => '1102',
|
||||
'account_name_zh' => '零用金',
|
||||
'account_name_en' => 'Petty Cash',
|
||||
'account_type' => 'asset',
|
||||
'category' => '流動資產',
|
||||
'display_order' => 20,
|
||||
],
|
||||
[
|
||||
'account_code' => '1201',
|
||||
'account_name_zh' => '銀行存款',
|
||||
'account_name_en' => 'Bank Deposits',
|
||||
'account_type' => 'asset',
|
||||
'category' => '流動資產',
|
||||
'display_order' => 30,
|
||||
],
|
||||
[
|
||||
'account_code' => '1301',
|
||||
'account_name_zh' => '應收帳款',
|
||||
'account_name_en' => 'Accounts Receivable',
|
||||
'account_type' => 'asset',
|
||||
'category' => '流動資產',
|
||||
'display_order' => 40,
|
||||
],
|
||||
[
|
||||
'account_code' => '1302',
|
||||
'account_name_zh' => '其他應收款',
|
||||
'account_name_en' => 'Other Receivables',
|
||||
'account_type' => 'asset',
|
||||
'category' => '流動資產',
|
||||
'display_order' => 50,
|
||||
],
|
||||
[
|
||||
'account_code' => '1401',
|
||||
'account_name_zh' => '土地',
|
||||
'account_name_en' => 'Land',
|
||||
'account_type' => 'asset',
|
||||
'category' => '固定資產',
|
||||
'display_order' => 60,
|
||||
],
|
||||
[
|
||||
'account_code' => '1402',
|
||||
'account_name_zh' => '房屋及建築',
|
||||
'account_name_en' => 'Buildings',
|
||||
'account_type' => 'asset',
|
||||
'category' => '固定資產',
|
||||
'display_order' => 70,
|
||||
],
|
||||
[
|
||||
'account_code' => '1403',
|
||||
'account_name_zh' => '機器設備',
|
||||
'account_name_en' => 'Machinery & Equipment',
|
||||
'account_type' => 'asset',
|
||||
'category' => '固定資產',
|
||||
'display_order' => 80,
|
||||
],
|
||||
[
|
||||
'account_code' => '1404',
|
||||
'account_name_zh' => '辦公設備',
|
||||
'account_name_en' => 'Office Equipment',
|
||||
'account_type' => 'asset',
|
||||
'category' => '固定資產',
|
||||
'display_order' => 90,
|
||||
],
|
||||
[
|
||||
'account_code' => '1405',
|
||||
'account_name_zh' => '電腦設備',
|
||||
'account_name_en' => 'Computer Equipment',
|
||||
'account_type' => 'asset',
|
||||
'category' => '固定資產',
|
||||
'display_order' => 100,
|
||||
],
|
||||
[
|
||||
'account_code' => '1501',
|
||||
'account_name_zh' => '存出保證金',
|
||||
'account_name_en' => 'Guarantee Deposits Paid',
|
||||
'account_type' => 'asset',
|
||||
'category' => '其他資產',
|
||||
'display_order' => 110,
|
||||
],
|
||||
|
||||
// Liabilities (負債) - 2xxx
|
||||
[
|
||||
'account_code' => '2101',
|
||||
'account_name_zh' => '應付帳款',
|
||||
'account_name_en' => 'Accounts Payable',
|
||||
'account_type' => 'liability',
|
||||
'category' => '流動負債',
|
||||
'display_order' => 200,
|
||||
],
|
||||
[
|
||||
'account_code' => '2102',
|
||||
'account_name_zh' => '應付薪資',
|
||||
'account_name_en' => 'Salaries Payable',
|
||||
'account_type' => 'liability',
|
||||
'category' => '流動負債',
|
||||
'display_order' => 210,
|
||||
],
|
||||
[
|
||||
'account_code' => '2103',
|
||||
'account_name_zh' => '應付費用',
|
||||
'account_name_en' => 'Accrued Expenses',
|
||||
'account_type' => 'liability',
|
||||
'category' => '流動負債',
|
||||
'display_order' => 220,
|
||||
],
|
||||
[
|
||||
'account_code' => '2104',
|
||||
'account_name_zh' => '代收款',
|
||||
'account_name_en' => 'Collections for Others',
|
||||
'account_type' => 'liability',
|
||||
'category' => '流動負債',
|
||||
'display_order' => 230,
|
||||
],
|
||||
[
|
||||
'account_code' => '2201',
|
||||
'account_name_zh' => '長期借款',
|
||||
'account_name_en' => 'Long-term Loans',
|
||||
'account_type' => 'liability',
|
||||
'category' => '長期負債',
|
||||
'display_order' => 240,
|
||||
],
|
||||
|
||||
// Net Assets/Fund Balance (淨資產/基金) - 3xxx
|
||||
[
|
||||
'account_code' => '3101',
|
||||
'account_name_zh' => '累積餘絀',
|
||||
'account_name_en' => 'Accumulated Surplus/Deficit',
|
||||
'account_type' => 'net_asset',
|
||||
'category' => '淨資產',
|
||||
'display_order' => 300,
|
||||
],
|
||||
[
|
||||
'account_code' => '3102',
|
||||
'account_name_zh' => '本期餘絀',
|
||||
'account_name_en' => 'Current Period Surplus/Deficit',
|
||||
'account_type' => 'net_asset',
|
||||
'category' => '淨資產',
|
||||
'display_order' => 310,
|
||||
],
|
||||
[
|
||||
'account_code' => '3201',
|
||||
'account_name_zh' => '基金',
|
||||
'account_name_en' => 'Fund Balance',
|
||||
'account_type' => 'net_asset',
|
||||
'category' => '基金',
|
||||
'display_order' => 320,
|
||||
],
|
||||
|
||||
// Income (收入) - 4xxx
|
||||
[
|
||||
'account_code' => '4101',
|
||||
'account_name_zh' => '會費收入',
|
||||
'account_name_en' => 'Membership Dues',
|
||||
'account_type' => 'income',
|
||||
'category' => '會費收入',
|
||||
'display_order' => 400,
|
||||
'description' => '會員繳交之常年會費',
|
||||
],
|
||||
[
|
||||
'account_code' => '4102',
|
||||
'account_name_zh' => '入會費收入',
|
||||
'account_name_en' => 'Entrance Fees',
|
||||
'account_type' => 'income',
|
||||
'category' => '會費收入',
|
||||
'display_order' => 410,
|
||||
'description' => '新會員入會費',
|
||||
],
|
||||
[
|
||||
'account_code' => '4201',
|
||||
'account_name_zh' => '捐贈收入',
|
||||
'account_name_en' => 'Donation Income',
|
||||
'account_type' => 'income',
|
||||
'category' => '捐贈收入',
|
||||
'display_order' => 420,
|
||||
'description' => '個人或團體捐贈',
|
||||
],
|
||||
[
|
||||
'account_code' => '4202',
|
||||
'account_name_zh' => '企業捐贈收入',
|
||||
'account_name_en' => 'Corporate Donations',
|
||||
'account_type' => 'income',
|
||||
'category' => '捐贈收入',
|
||||
'display_order' => 430,
|
||||
'description' => '企業捐贈',
|
||||
],
|
||||
[
|
||||
'account_code' => '4301',
|
||||
'account_name_zh' => '政府補助收入',
|
||||
'account_name_en' => 'Government Grants',
|
||||
'account_type' => 'income',
|
||||
'category' => '補助收入',
|
||||
'display_order' => 440,
|
||||
'description' => '政府機關補助款',
|
||||
],
|
||||
[
|
||||
'account_code' => '4302',
|
||||
'account_name_zh' => '計畫補助收入',
|
||||
'account_name_en' => 'Project Grants',
|
||||
'account_type' => 'income',
|
||||
'category' => '補助收入',
|
||||
'display_order' => 450,
|
||||
'description' => '專案計畫補助',
|
||||
],
|
||||
[
|
||||
'account_code' => '4401',
|
||||
'account_name_zh' => '利息收入',
|
||||
'account_name_en' => 'Interest Income',
|
||||
'account_type' => 'income',
|
||||
'category' => '其他收入',
|
||||
'display_order' => 460,
|
||||
],
|
||||
[
|
||||
'account_code' => '4402',
|
||||
'account_name_zh' => '活動報名費收入',
|
||||
'account_name_en' => 'Activity Registration Fees',
|
||||
'account_type' => 'income',
|
||||
'category' => '其他收入',
|
||||
'display_order' => 470,
|
||||
'description' => '各項活動報名費',
|
||||
],
|
||||
[
|
||||
'account_code' => '4901',
|
||||
'account_name_zh' => '雜項收入',
|
||||
'account_name_en' => 'Miscellaneous Income',
|
||||
'account_type' => 'income',
|
||||
'category' => '其他收入',
|
||||
'display_order' => 480,
|
||||
],
|
||||
|
||||
// Expenses (支出) - 5xxx
|
||||
// Personnel Expenses (人事費)
|
||||
[
|
||||
'account_code' => '5101',
|
||||
'account_name_zh' => '薪資支出',
|
||||
'account_name_en' => 'Salaries & Wages',
|
||||
'account_type' => 'expense',
|
||||
'category' => '人事費',
|
||||
'display_order' => 500,
|
||||
],
|
||||
[
|
||||
'account_code' => '5102',
|
||||
'account_name_zh' => '勞健保費',
|
||||
'account_name_en' => 'Labor & Health Insurance',
|
||||
'account_type' => 'expense',
|
||||
'category' => '人事費',
|
||||
'display_order' => 510,
|
||||
],
|
||||
[
|
||||
'account_code' => '5103',
|
||||
'account_name_zh' => '退休金提撥',
|
||||
'account_name_en' => 'Pension Contributions',
|
||||
'account_type' => 'expense',
|
||||
'category' => '人事費',
|
||||
'display_order' => 520,
|
||||
],
|
||||
[
|
||||
'account_code' => '5104',
|
||||
'account_name_zh' => '加班費',
|
||||
'account_name_en' => 'Overtime Pay',
|
||||
'account_type' => 'expense',
|
||||
'category' => '人事費',
|
||||
'display_order' => 530,
|
||||
],
|
||||
[
|
||||
'account_code' => '5105',
|
||||
'account_name_zh' => '員工福利',
|
||||
'account_name_en' => 'Employee Benefits',
|
||||
'account_type' => 'expense',
|
||||
'category' => '人事費',
|
||||
'display_order' => 540,
|
||||
],
|
||||
|
||||
// Operating Expenses (業務費)
|
||||
[
|
||||
'account_code' => '5201',
|
||||
'account_name_zh' => '租金支出',
|
||||
'account_name_en' => 'Rent Expense',
|
||||
'account_type' => 'expense',
|
||||
'category' => '業務費',
|
||||
'display_order' => 550,
|
||||
],
|
||||
[
|
||||
'account_code' => '5202',
|
||||
'account_name_zh' => '水電費',
|
||||
'account_name_en' => 'Utilities',
|
||||
'account_type' => 'expense',
|
||||
'category' => '業務費',
|
||||
'display_order' => 560,
|
||||
],
|
||||
[
|
||||
'account_code' => '5203',
|
||||
'account_name_zh' => '郵電費',
|
||||
'account_name_en' => 'Postage & Telecommunications',
|
||||
'account_type' => 'expense',
|
||||
'category' => '業務費',
|
||||
'display_order' => 570,
|
||||
],
|
||||
[
|
||||
'account_code' => '5204',
|
||||
'account_name_zh' => '文具用品',
|
||||
'account_name_en' => 'Office Supplies',
|
||||
'account_type' => 'expense',
|
||||
'category' => '業務費',
|
||||
'display_order' => 580,
|
||||
],
|
||||
[
|
||||
'account_code' => '5205',
|
||||
'account_name_zh' => '印刷費',
|
||||
'account_name_en' => 'Printing Expenses',
|
||||
'account_type' => 'expense',
|
||||
'category' => '業務費',
|
||||
'display_order' => 590,
|
||||
],
|
||||
[
|
||||
'account_code' => '5206',
|
||||
'account_name_zh' => '旅運費',
|
||||
'account_name_en' => 'Travel & Transportation',
|
||||
'account_type' => 'expense',
|
||||
'category' => '業務費',
|
||||
'display_order' => 600,
|
||||
],
|
||||
[
|
||||
'account_code' => '5207',
|
||||
'account_name_zh' => '保險費',
|
||||
'account_name_en' => 'Insurance Premiums',
|
||||
'account_type' => 'expense',
|
||||
'category' => '業務費',
|
||||
'display_order' => 610,
|
||||
],
|
||||
[
|
||||
'account_code' => '5208',
|
||||
'account_name_zh' => '修繕費',
|
||||
'account_name_en' => 'Repairs & Maintenance',
|
||||
'account_type' => 'expense',
|
||||
'category' => '業務費',
|
||||
'display_order' => 620,
|
||||
],
|
||||
[
|
||||
'account_code' => '5209',
|
||||
'account_name_zh' => '會議費',
|
||||
'account_name_en' => 'Meeting Expenses',
|
||||
'account_type' => 'expense',
|
||||
'category' => '業務費',
|
||||
'display_order' => 630,
|
||||
],
|
||||
|
||||
// Program/Activity Expenses (活動費)
|
||||
[
|
||||
'account_code' => '5301',
|
||||
'account_name_zh' => '活動場地費',
|
||||
'account_name_en' => 'Activity Venue Rental',
|
||||
'account_type' => 'expense',
|
||||
'category' => '活動費',
|
||||
'display_order' => 640,
|
||||
],
|
||||
[
|
||||
'account_code' => '5302',
|
||||
'account_name_zh' => '活動講師費',
|
||||
'account_name_en' => 'Speaker/Instructor Fees',
|
||||
'account_type' => 'expense',
|
||||
'category' => '活動費',
|
||||
'display_order' => 650,
|
||||
],
|
||||
[
|
||||
'account_code' => '5303',
|
||||
'account_name_zh' => '活動餐費',
|
||||
'account_name_en' => 'Activity Catering',
|
||||
'account_type' => 'expense',
|
||||
'category' => '活動費',
|
||||
'display_order' => 660,
|
||||
],
|
||||
[
|
||||
'account_code' => '5304',
|
||||
'account_name_zh' => '活動材料費',
|
||||
'account_name_en' => 'Activity Materials',
|
||||
'account_type' => 'expense',
|
||||
'category' => '活動費',
|
||||
'display_order' => 670,
|
||||
],
|
||||
[
|
||||
'account_code' => '5305',
|
||||
'account_name_zh' => '活動宣傳費',
|
||||
'account_name_en' => 'Activity Promotion',
|
||||
'account_type' => 'expense',
|
||||
'category' => '活動費',
|
||||
'display_order' => 680,
|
||||
],
|
||||
|
||||
// Administrative Expenses (行政管理費)
|
||||
[
|
||||
'account_code' => '5401',
|
||||
'account_name_zh' => '稅捐',
|
||||
'account_name_en' => 'Taxes',
|
||||
'account_type' => 'expense',
|
||||
'category' => '行政管理費',
|
||||
'display_order' => 690,
|
||||
],
|
||||
[
|
||||
'account_code' => '5402',
|
||||
'account_name_zh' => '規費',
|
||||
'account_name_en' => 'Administrative Fees',
|
||||
'account_type' => 'expense',
|
||||
'category' => '行政管理費',
|
||||
'display_order' => 700,
|
||||
],
|
||||
[
|
||||
'account_code' => '5403',
|
||||
'account_name_zh' => '銀行手續費',
|
||||
'account_name_en' => 'Bank Service Charges',
|
||||
'account_type' => 'expense',
|
||||
'category' => '行政管理費',
|
||||
'display_order' => 710,
|
||||
],
|
||||
[
|
||||
'account_code' => '5404',
|
||||
'account_name_zh' => '電腦網路費',
|
||||
'account_name_en' => 'IT & Network Expenses',
|
||||
'account_type' => 'expense',
|
||||
'category' => '行政管理費',
|
||||
'display_order' => 720,
|
||||
],
|
||||
[
|
||||
'account_code' => '5405',
|
||||
'account_name_zh' => '專業服務費',
|
||||
'account_name_en' => 'Professional Services',
|
||||
'account_type' => 'expense',
|
||||
'category' => '行政管理費',
|
||||
'display_order' => 730,
|
||||
'description' => '會計師、律師等專業服務費',
|
||||
],
|
||||
[
|
||||
'account_code' => '5406',
|
||||
'account_name_zh' => '折舊費用',
|
||||
'account_name_en' => 'Depreciation',
|
||||
'account_type' => 'expense',
|
||||
'category' => '行政管理費',
|
||||
'display_order' => 740,
|
||||
],
|
||||
|
||||
// Other Expenses (其他支出)
|
||||
[
|
||||
'account_code' => '5901',
|
||||
'account_name_zh' => '雜項支出',
|
||||
'account_name_en' => 'Miscellaneous Expenses',
|
||||
'account_type' => 'expense',
|
||||
'category' => '其他支出',
|
||||
'display_order' => 750,
|
||||
],
|
||||
[
|
||||
'account_code' => '5902',
|
||||
'account_name_zh' => '呆帳損失',
|
||||
'account_name_en' => 'Bad Debt Expense',
|
||||
'account_type' => 'expense',
|
||||
'category' => '其他支出',
|
||||
'display_order' => 760,
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
ChartOfAccount::create($account);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
database/seeders/DatabaseSeeder.php
Normal file
22
database/seeders/DatabaseSeeder.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class DatabaseSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Seed the application's database.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// \App\Models\User::factory(10)->create();
|
||||
|
||||
// \App\Models\User::factory()->create([
|
||||
// 'name' => 'Test User',
|
||||
// 'email' => 'test@example.com',
|
||||
// ]);
|
||||
}
|
||||
}
|
||||
75
database/seeders/DocumentCategorySeeder.php
Normal file
75
database/seeders/DocumentCategorySeeder.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\DocumentCategory;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class DocumentCategorySeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$categories = [
|
||||
[
|
||||
'name' => '協會辦法',
|
||||
'slug' => 'bylaws',
|
||||
'description' => '協會章程、組織辦法等基本規範文件',
|
||||
'icon' => '📜',
|
||||
'sort_order' => 1,
|
||||
'default_access_level' => 'public',
|
||||
],
|
||||
[
|
||||
'name' => '法規與規範',
|
||||
'slug' => 'regulations',
|
||||
'description' => '內部規章、管理辦法、作業規範',
|
||||
'icon' => '📋',
|
||||
'sort_order' => 2,
|
||||
'default_access_level' => 'members',
|
||||
],
|
||||
[
|
||||
'name' => '會議記錄',
|
||||
'slug' => 'meeting-minutes',
|
||||
'description' => '理事會、會員大會、各委員會會議記錄',
|
||||
'icon' => '📝',
|
||||
'sort_order' => 3,
|
||||
'default_access_level' => 'members',
|
||||
],
|
||||
[
|
||||
'name' => '表格與申請書',
|
||||
'slug' => 'forms',
|
||||
'description' => '各類申請表格、會員服務表單',
|
||||
'icon' => '📄',
|
||||
'sort_order' => 4,
|
||||
'default_access_level' => 'public',
|
||||
],
|
||||
[
|
||||
'name' => '年度報告',
|
||||
'slug' => 'annual-reports',
|
||||
'description' => '年度工作報告、財務報告',
|
||||
'icon' => '📊',
|
||||
'sort_order' => 5,
|
||||
'default_access_level' => 'members',
|
||||
],
|
||||
[
|
||||
'name' => '活動文件',
|
||||
'slug' => 'events',
|
||||
'description' => '活動企劃、執行報告、相關文件',
|
||||
'icon' => '🎯',
|
||||
'sort_order' => 6,
|
||||
'default_access_level' => 'members',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($categories as $category) {
|
||||
DocumentCategory::updateOrCreate(
|
||||
['slug' => $category['slug']],
|
||||
$category
|
||||
);
|
||||
}
|
||||
|
||||
$this->command->info('Document categories seeded successfully!');
|
||||
}
|
||||
}
|
||||
176
database/seeders/FinancialWorkflowPermissionsSeeder.php
Normal file
176
database/seeders/FinancialWorkflowPermissionsSeeder.php
Normal file
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class FinancialWorkflowPermissionsSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// Create permissions for financial workflow
|
||||
$permissions = [
|
||||
// Approval Stage Permissions
|
||||
'approve_finance_cashier' => '出納審核財務申請單(第一階段)',
|
||||
'approve_finance_accountant' => '會計審核財務申請單(第二階段)',
|
||||
'approve_finance_chair' => '理事長審核財務申請單(第三階段)',
|
||||
'approve_finance_board' => '理事會審核大額財務申請(大於50,000)',
|
||||
|
||||
// Payment Stage Permissions
|
||||
'create_payment_order' => '會計製作付款單',
|
||||
'verify_payment_order' => '出納覆核付款單',
|
||||
'execute_payment' => '出納執行付款',
|
||||
'upload_payment_receipt' => '上傳付款憑證',
|
||||
|
||||
// Recording Stage Permissions
|
||||
'record_cashier_ledger' => '出納記錄現金簿',
|
||||
'record_accounting_transaction' => '會計記錄會計分錄',
|
||||
'view_cashier_ledger' => '查看出納現金簿',
|
||||
'view_accounting_transactions' => '查看會計分錄',
|
||||
|
||||
// Reconciliation Permissions
|
||||
'prepare_bank_reconciliation' => '出納製作銀行調節表',
|
||||
'review_bank_reconciliation' => '會計覆核銀行調節表',
|
||||
'approve_bank_reconciliation' => '主管核准銀行調節表',
|
||||
|
||||
// General Finance Document Permissions
|
||||
'view_finance_documents' => '查看財務申請單',
|
||||
'create_finance_documents' => '建立財務申請單',
|
||||
'edit_finance_documents' => '編輯財務申請單',
|
||||
'delete_finance_documents' => '刪除財務申請單',
|
||||
|
||||
// Chart of Accounts & Budget Permissions
|
||||
'assign_chart_of_account' => '指定會計科目',
|
||||
'assign_budget_item' => '指定預算項目',
|
||||
|
||||
// Dashboard & Reports Permissions
|
||||
'view_finance_dashboard' => '查看財務儀表板',
|
||||
'view_finance_reports' => '查看財務報表',
|
||||
'export_finance_reports' => '匯出財務報表',
|
||||
];
|
||||
|
||||
foreach ($permissions as $name => $description) {
|
||||
Permission::firstOrCreate(
|
||||
['name' => $name],
|
||||
['guard_name' => 'web']
|
||||
);
|
||||
$this->command->info("Permission created: {$name}");
|
||||
}
|
||||
|
||||
// Create roles for financial workflow
|
||||
$roles = [
|
||||
'finance_cashier' => [
|
||||
'permissions' => [
|
||||
// Approval stage
|
||||
'approve_finance_cashier',
|
||||
// Payment stage
|
||||
'verify_payment_order',
|
||||
'execute_payment',
|
||||
'upload_payment_receipt',
|
||||
// Recording stage
|
||||
'record_cashier_ledger',
|
||||
'view_cashier_ledger',
|
||||
// Reconciliation
|
||||
'prepare_bank_reconciliation',
|
||||
// General
|
||||
'view_finance_documents',
|
||||
'view_finance_dashboard',
|
||||
],
|
||||
'description' => '出納 - 管錢(覆核付款單、執行付款、記錄現金簿、製作銀行調節表)',
|
||||
],
|
||||
'finance_accountant' => [
|
||||
'permissions' => [
|
||||
// Approval stage
|
||||
'approve_finance_accountant',
|
||||
// Payment stage
|
||||
'create_payment_order',
|
||||
// Recording stage
|
||||
'record_accounting_transaction',
|
||||
'view_accounting_transactions',
|
||||
// Reconciliation
|
||||
'review_bank_reconciliation',
|
||||
// Chart of accounts & budget
|
||||
'assign_chart_of_account',
|
||||
'assign_budget_item',
|
||||
// General
|
||||
'view_finance_documents',
|
||||
'view_finance_dashboard',
|
||||
'view_finance_reports',
|
||||
'export_finance_reports',
|
||||
],
|
||||
'description' => '會計 - 管帳(製作付款單、記錄會計分錄、覆核銀行調節表、指定會計科目)',
|
||||
],
|
||||
'finance_chair' => [
|
||||
'permissions' => [
|
||||
// Approval stage
|
||||
'approve_finance_chair',
|
||||
// Reconciliation
|
||||
'approve_bank_reconciliation',
|
||||
// General
|
||||
'view_finance_documents',
|
||||
'view_finance_dashboard',
|
||||
'view_finance_reports',
|
||||
'export_finance_reports',
|
||||
],
|
||||
'description' => '理事長 - 審核中大額財務申請、核准銀行調節表',
|
||||
],
|
||||
'finance_board_member' => [
|
||||
'permissions' => [
|
||||
// Approval stage (for large amounts)
|
||||
'approve_finance_board',
|
||||
// General
|
||||
'view_finance_documents',
|
||||
'view_finance_dashboard',
|
||||
'view_finance_reports',
|
||||
],
|
||||
'description' => '理事 - 審核大額財務申請(大於50,000)',
|
||||
],
|
||||
'finance_requester' => [
|
||||
'permissions' => [
|
||||
'view_finance_documents',
|
||||
'create_finance_documents',
|
||||
'edit_finance_documents',
|
||||
],
|
||||
'description' => '財務申請人 - 可建立和編輯自己的財務申請單',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($roles as $roleName => $roleData) {
|
||||
$role = Role::firstOrCreate(
|
||||
['name' => $roleName],
|
||||
['guard_name' => 'web']
|
||||
);
|
||||
|
||||
// Assign permissions to role
|
||||
$role->syncPermissions($roleData['permissions']);
|
||||
|
||||
$this->command->info("Role created: {$roleName} with permissions: " . implode(', ', $roleData['permissions']));
|
||||
}
|
||||
|
||||
// Assign all financial workflow permissions to admin role (if exists)
|
||||
$adminRole = Role::where('name', 'admin')->first();
|
||||
if ($adminRole) {
|
||||
$adminRole->givePermissionTo(array_keys($permissions));
|
||||
$this->command->info("Admin role updated with all financial workflow permissions");
|
||||
}
|
||||
|
||||
$this->command->info("\n=== Financial Workflow Roles & Permissions Created ===");
|
||||
$this->command->info("Roles created:");
|
||||
$this->command->info("1. finance_cashier - 出納(管錢)");
|
||||
$this->command->info("2. finance_accountant - 會計(管帳)");
|
||||
$this->command->info("3. finance_chair - 理事長");
|
||||
$this->command->info("4. finance_board_member - 理事");
|
||||
$this->command->info("5. finance_requester - 財務申請人");
|
||||
$this->command->info("\nWorkflow stages:");
|
||||
$this->command->info("1. Approval Stage: Cashier → Accountant → Chair (→ Board for large amounts)");
|
||||
$this->command->info("2. Payment Stage: Accountant creates order → Cashier verifies → Cashier executes");
|
||||
$this->command->info("3. Recording Stage: Cashier records ledger + Accountant records transactions");
|
||||
$this->command->info("4. Reconciliation: Cashier prepares → Accountant reviews → Chair approves");
|
||||
}
|
||||
}
|
||||
393
database/seeders/FinancialWorkflowTestDataSeeder.php
Normal file
393
database/seeders/FinancialWorkflowTestDataSeeder.php
Normal file
@@ -0,0 +1,393 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\BankReconciliation;
|
||||
use App\Models\CashierLedgerEntry;
|
||||
use App\Models\FinanceDocument;
|
||||
use App\Models\PaymentOrder;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
/**
|
||||
* Financial Workflow Test Data Seeder
|
||||
*
|
||||
* Generates comprehensive test data for the financial workflow system
|
||||
*/
|
||||
class FinancialWorkflowTestDataSeeder extends Seeder
|
||||
{
|
||||
protected User $requester;
|
||||
protected User $cashier;
|
||||
protected User $accountant;
|
||||
protected User $chair;
|
||||
protected User $boardMember;
|
||||
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$this->command->info('🌱 Seeding financial workflow test data...');
|
||||
|
||||
// Create or get test users
|
||||
$this->createTestUsers();
|
||||
|
||||
// Seed finance documents at various stages
|
||||
$this->seedFinanceDocuments();
|
||||
|
||||
// Seed payment orders
|
||||
$this->seedPaymentOrders();
|
||||
|
||||
// Seed cashier ledger entries
|
||||
$this->seedCashierLedgerEntries();
|
||||
|
||||
// Seed bank reconciliations
|
||||
$this->seedBankReconciliations();
|
||||
|
||||
$this->command->info('✅ Financial workflow test data seeded successfully!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create test users with appropriate roles
|
||||
*/
|
||||
protected function createTestUsers(): void
|
||||
{
|
||||
$this->command->info('Creating test users...');
|
||||
|
||||
$this->requester = User::firstOrCreate(
|
||||
['email' => 'requester@test.com'],
|
||||
[
|
||||
'name' => 'Test Requester',
|
||||
'password' => Hash::make('password'),
|
||||
]
|
||||
);
|
||||
$this->requester->assignRole('finance_requester');
|
||||
|
||||
$this->cashier = User::firstOrCreate(
|
||||
['email' => 'cashier@test.com'],
|
||||
[
|
||||
'name' => 'Test Cashier',
|
||||
'password' => Hash::make('password'),
|
||||
]
|
||||
);
|
||||
$this->cashier->assignRole('finance_cashier');
|
||||
|
||||
$this->accountant = User::firstOrCreate(
|
||||
['email' => 'accountant@test.com'],
|
||||
[
|
||||
'name' => 'Test Accountant',
|
||||
'password' => Hash::make('password'),
|
||||
]
|
||||
);
|
||||
$this->accountant->assignRole('finance_accountant');
|
||||
|
||||
$this->chair = User::firstOrCreate(
|
||||
['email' => 'chair@test.com'],
|
||||
[
|
||||
'name' => 'Test Chair',
|
||||
'password' => Hash::make('password'),
|
||||
]
|
||||
);
|
||||
$this->chair->assignRole('finance_chair');
|
||||
|
||||
$this->boardMember = User::firstOrCreate(
|
||||
['email' => 'board@test.com'],
|
||||
[
|
||||
'name' => 'Test Board Member',
|
||||
'password' => Hash::make('password'),
|
||||
]
|
||||
);
|
||||
$this->boardMember->assignRole('finance_board_member');
|
||||
|
||||
$this->command->info('✓ Test users created');
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed finance documents at various stages of the workflow
|
||||
*/
|
||||
protected function seedFinanceDocuments(): void
|
||||
{
|
||||
$this->command->info('Seeding finance documents...');
|
||||
|
||||
// Pending documents (Stage 1)
|
||||
FinanceDocument::factory()
|
||||
->count(3)
|
||||
->smallAmount()
|
||||
->pending()
|
||||
->create(['submitted_by_id' => $this->requester->id]);
|
||||
|
||||
FinanceDocument::factory()
|
||||
->count(2)
|
||||
->mediumAmount()
|
||||
->pending()
|
||||
->create(['submitted_by_id' => $this->requester->id]);
|
||||
|
||||
// Approved by cashier (Stage 1)
|
||||
FinanceDocument::factory()
|
||||
->count(2)
|
||||
->smallAmount()
|
||||
->approvedByCashier()
|
||||
->create([
|
||||
'submitted_by_id' => $this->requester->id,
|
||||
'cashier_approved_by_id' => $this->cashier->id,
|
||||
]);
|
||||
|
||||
// Approved by accountant - small amounts (Ready for payment)
|
||||
FinanceDocument::factory()
|
||||
->count(3)
|
||||
->smallAmount()
|
||||
->approvedByAccountant()
|
||||
->create([
|
||||
'submitted_by_id' => $this->requester->id,
|
||||
'cashier_approved_by_id' => $this->cashier->id,
|
||||
'accountant_approved_by_id' => $this->accountant->id,
|
||||
]);
|
||||
|
||||
// Approved by chair - medium amounts (Ready for payment)
|
||||
FinanceDocument::factory()
|
||||
->count(2)
|
||||
->mediumAmount()
|
||||
->approvedByChair()
|
||||
->create([
|
||||
'submitted_by_id' => $this->requester->id,
|
||||
'cashier_approved_by_id' => $this->cashier->id,
|
||||
'accountant_approved_by_id' => $this->accountant->id,
|
||||
'chair_approved_by_id' => $this->chair->id,
|
||||
]);
|
||||
|
||||
// Large amount with board approval (Ready for payment)
|
||||
FinanceDocument::factory()
|
||||
->count(1)
|
||||
->largeAmount()
|
||||
->approvedByChair()
|
||||
->create([
|
||||
'submitted_by_id' => $this->requester->id,
|
||||
'cashier_approved_by_id' => $this->cashier->id,
|
||||
'accountant_approved_by_id' => $this->accountant->id,
|
||||
'chair_approved_by_id' => $this->chair->id,
|
||||
'board_meeting_approved_at' => now(),
|
||||
'board_meeting_approved_by_id' => $this->boardMember->id,
|
||||
]);
|
||||
|
||||
// Completed workflow
|
||||
FinanceDocument::factory()
|
||||
->count(5)
|
||||
->smallAmount()
|
||||
->approvedByAccountant()
|
||||
->paymentExecuted()
|
||||
->create([
|
||||
'submitted_by_id' => $this->requester->id,
|
||||
'cashier_approved_by_id' => $this->cashier->id,
|
||||
'accountant_approved_by_id' => $this->accountant->id,
|
||||
'cashier_recorded_at' => now(),
|
||||
]);
|
||||
|
||||
// Rejected documents
|
||||
FinanceDocument::factory()
|
||||
->count(2)
|
||||
->rejected()
|
||||
->create([
|
||||
'submitted_by_id' => $this->requester->id,
|
||||
]);
|
||||
|
||||
$this->command->info('✓ Finance documents seeded');
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed payment orders
|
||||
*/
|
||||
protected function seedPaymentOrders(): void
|
||||
{
|
||||
$this->command->info('Seeding payment orders...');
|
||||
|
||||
// Get approved documents without payment orders
|
||||
$approvedDocs = FinanceDocument::whereIn('status', [
|
||||
FinanceDocument::STATUS_APPROVED_ACCOUNTANT,
|
||||
FinanceDocument::STATUS_APPROVED_CHAIR,
|
||||
])
|
||||
->whereNull('payment_order_created_at')
|
||||
->limit(5)
|
||||
->get();
|
||||
|
||||
foreach ($approvedDocs as $doc) {
|
||||
// Pending verification
|
||||
PaymentOrder::factory()
|
||||
->pendingVerification()
|
||||
->create([
|
||||
'finance_document_id' => $doc->id,
|
||||
'payment_amount' => $doc->amount,
|
||||
'created_by_accountant_id' => $this->accountant->id,
|
||||
]);
|
||||
|
||||
$doc->update([
|
||||
'payment_order_created_at' => now(),
|
||||
'payment_order_created_by_id' => $this->accountant->id,
|
||||
]);
|
||||
}
|
||||
|
||||
// Verified payment orders
|
||||
PaymentOrder::factory()
|
||||
->count(3)
|
||||
->verified()
|
||||
->create([
|
||||
'created_by_accountant_id' => $this->accountant->id,
|
||||
'verified_by_cashier_id' => $this->cashier->id,
|
||||
]);
|
||||
|
||||
// Executed payment orders
|
||||
PaymentOrder::factory()
|
||||
->count(5)
|
||||
->executed()
|
||||
->create([
|
||||
'created_by_accountant_id' => $this->accountant->id,
|
||||
'verified_by_cashier_id' => $this->cashier->id,
|
||||
'executed_by_cashier_id' => $this->cashier->id,
|
||||
]);
|
||||
|
||||
$this->command->info('✓ Payment orders seeded');
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed cashier ledger entries with running balances
|
||||
*/
|
||||
protected function seedCashierLedgerEntries(): void
|
||||
{
|
||||
$this->command->info('Seeding cashier ledger entries...');
|
||||
|
||||
$bankAccounts = [
|
||||
'First Bank - 1234567890',
|
||||
'Second Bank - 0987654321',
|
||||
'Petty Cash',
|
||||
];
|
||||
|
||||
foreach ($bankAccounts as $account) {
|
||||
$currentBalance = 100000; // Starting balance
|
||||
|
||||
// Create 10 entries for each account
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$isReceipt = $i % 3 !== 0; // 2/3 receipts, 1/3 payments
|
||||
$amount = rand(1000, 10000);
|
||||
|
||||
$entry = CashierLedgerEntry::create([
|
||||
'entry_type' => $isReceipt ? 'receipt' : 'payment',
|
||||
'entry_date' => now()->subDays(rand(1, 30)),
|
||||
'amount' => $amount,
|
||||
'payment_method' => $account === 'Petty Cash' ? 'cash' : 'bank_transfer',
|
||||
'bank_account' => $account,
|
||||
'balance_before' => $currentBalance,
|
||||
'balance_after' => $isReceipt
|
||||
? $currentBalance + $amount
|
||||
: $currentBalance - $amount,
|
||||
'receipt_number' => $isReceipt ? 'RCP' . str_pad($i + 1, 6, '0', STR_PAD_LEFT) : null,
|
||||
'notes' => $isReceipt ? 'Test receipt entry' : 'Test payment entry',
|
||||
'recorded_by_cashier_id' => $this->cashier->id,
|
||||
'recorded_at' => now()->subDays(rand(1, 30)),
|
||||
]);
|
||||
|
||||
$currentBalance = $entry->balance_after;
|
||||
}
|
||||
}
|
||||
|
||||
$this->command->info('✓ Cashier ledger entries seeded');
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed bank reconciliations
|
||||
*/
|
||||
protected function seedBankReconciliations(): void
|
||||
{
|
||||
$this->command->info('Seeding bank reconciliations...');
|
||||
|
||||
// Pending reconciliation
|
||||
BankReconciliation::create([
|
||||
'reconciliation_month' => now()->startOfMonth(),
|
||||
'bank_statement_date' => now(),
|
||||
'bank_statement_balance' => 100000,
|
||||
'system_book_balance' => 95000,
|
||||
'outstanding_checks' => [
|
||||
['check_number' => 'CHK001', 'amount' => 3000, 'description' => 'Vendor A payment'],
|
||||
['check_number' => 'CHK002', 'amount' => 2000, 'description' => 'Service fee'],
|
||||
],
|
||||
'deposits_in_transit' => [
|
||||
['date' => now()->format('Y-m-d'), 'amount' => 5000, 'description' => 'Member dues'],
|
||||
],
|
||||
'bank_charges' => [
|
||||
['amount' => 500, 'description' => 'Monthly service charge'],
|
||||
],
|
||||
'discrepancy_amount' => 4500,
|
||||
'notes' => 'Pending review',
|
||||
'prepared_by_cashier_id' => $this->cashier->id,
|
||||
'prepared_at' => now(),
|
||||
'reconciliation_status' => 'pending',
|
||||
]);
|
||||
|
||||
// Reviewed reconciliation
|
||||
BankReconciliation::create([
|
||||
'reconciliation_month' => now()->subMonth()->startOfMonth(),
|
||||
'bank_statement_date' => now()->subMonth(),
|
||||
'bank_statement_balance' => 95000,
|
||||
'system_book_balance' => 93000,
|
||||
'outstanding_checks' => [
|
||||
['check_number' => 'CHK003', 'amount' => 1500, 'description' => 'Supplies'],
|
||||
],
|
||||
'deposits_in_transit' => [
|
||||
['date' => now()->subMonth()->format('Y-m-d'), 'amount' => 3000, 'description' => 'Donation'],
|
||||
],
|
||||
'bank_charges' => [
|
||||
['amount' => 500, 'description' => 'Service charge'],
|
||||
],
|
||||
'discrepancy_amount' => 0,
|
||||
'notes' => 'All items reconciled',
|
||||
'prepared_by_cashier_id' => $this->cashier->id,
|
||||
'prepared_at' => now()->subMonth(),
|
||||
'reviewed_by_accountant_id' => $this->accountant->id,
|
||||
'reviewed_at' => now()->subMonth()->addDays(2),
|
||||
'reconciliation_status' => 'pending',
|
||||
]);
|
||||
|
||||
// Completed reconciliation
|
||||
BankReconciliation::create([
|
||||
'reconciliation_month' => now()->subMonths(2)->startOfMonth(),
|
||||
'bank_statement_date' => now()->subMonths(2),
|
||||
'bank_statement_balance' => 90000,
|
||||
'system_book_balance' => 90000,
|
||||
'outstanding_checks' => [],
|
||||
'deposits_in_transit' => [],
|
||||
'bank_charges' => [
|
||||
['amount' => 500, 'description' => 'Service charge'],
|
||||
],
|
||||
'discrepancy_amount' => 0,
|
||||
'notes' => 'Perfect match',
|
||||
'prepared_by_cashier_id' => $this->cashier->id,
|
||||
'prepared_at' => now()->subMonths(2),
|
||||
'reviewed_by_accountant_id' => $this->accountant->id,
|
||||
'reviewed_at' => now()->subMonths(2)->addDays(2),
|
||||
'approved_by_manager_id' => $this->chair->id,
|
||||
'approved_at' => now()->subMonths(2)->addDays(3),
|
||||
'reconciliation_status' => 'completed',
|
||||
]);
|
||||
|
||||
// Reconciliation with discrepancy
|
||||
BankReconciliation::create([
|
||||
'reconciliation_month' => now()->subMonths(3)->startOfMonth(),
|
||||
'bank_statement_date' => now()->subMonths(3),
|
||||
'bank_statement_balance' => 85000,
|
||||
'system_book_balance' => 75000,
|
||||
'outstanding_checks' => [
|
||||
['check_number' => 'CHK004', 'amount' => 2000, 'description' => 'Payment'],
|
||||
],
|
||||
'deposits_in_transit' => [],
|
||||
'bank_charges' => [
|
||||
['amount' => 500, 'description' => 'Service charge'],
|
||||
],
|
||||
'discrepancy_amount' => 7500,
|
||||
'notes' => 'Large discrepancy - needs investigation',
|
||||
'prepared_by_cashier_id' => $this->cashier->id,
|
||||
'prepared_at' => now()->subMonths(3),
|
||||
'reconciliation_status' => 'discrepancy',
|
||||
]);
|
||||
|
||||
$this->command->info('✓ Bank reconciliations seeded');
|
||||
}
|
||||
}
|
||||
86
database/seeders/IssueLabelSeeder.php
Normal file
86
database/seeders/IssueLabelSeeder.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\IssueLabel;
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class IssueLabelSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$labels = [
|
||||
[
|
||||
'name' => 'urgent',
|
||||
'color' => '#DC2626',
|
||||
'description' => 'Requires immediate attention',
|
||||
],
|
||||
[
|
||||
'name' => 'bug',
|
||||
'color' => '#EF4444',
|
||||
'description' => 'Something is not working correctly',
|
||||
],
|
||||
[
|
||||
'name' => 'enhancement',
|
||||
'color' => '#3B82F6',
|
||||
'description' => 'New feature or improvement request',
|
||||
],
|
||||
[
|
||||
'name' => 'documentation',
|
||||
'color' => '#10B981',
|
||||
'description' => 'Documentation related task',
|
||||
],
|
||||
[
|
||||
'name' => 'member-facing',
|
||||
'color' => '#8B5CF6',
|
||||
'description' => 'Affects members directly',
|
||||
],
|
||||
[
|
||||
'name' => 'internal',
|
||||
'color' => '#F59E0B',
|
||||
'description' => 'Internal staff operations',
|
||||
],
|
||||
[
|
||||
'name' => 'event',
|
||||
'color' => '#EC4899',
|
||||
'description' => 'Event planning or execution',
|
||||
],
|
||||
[
|
||||
'name' => 'finance',
|
||||
'color' => '#14B8A6',
|
||||
'description' => 'Financial or budget related',
|
||||
],
|
||||
[
|
||||
'name' => 'communications',
|
||||
'color' => '#6366F1',
|
||||
'description' => 'Marketing, PR, or communications',
|
||||
],
|
||||
[
|
||||
'name' => 'blocked',
|
||||
'color' => '#64748B',
|
||||
'description' => 'Blocked by another issue or dependency',
|
||||
],
|
||||
[
|
||||
'name' => 'help-wanted',
|
||||
'color' => '#22C55E',
|
||||
'description' => 'Looking for volunteers or assistance',
|
||||
],
|
||||
[
|
||||
'name' => 'question',
|
||||
'color' => '#A855F7',
|
||||
'description' => 'Question or clarification needed',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($labels as $label) {
|
||||
IssueLabel::updateOrCreate(
|
||||
['name' => $label['name']],
|
||||
$label
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
79
database/seeders/PaymentVerificationRolesSeeder.php
Normal file
79
database/seeders/PaymentVerificationRolesSeeder.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class PaymentVerificationRolesSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// Create permissions for payment verification workflow
|
||||
$permissions = [
|
||||
'verify_payments_cashier' => 'Verify membership payments as cashier (Tier 1)',
|
||||
'verify_payments_accountant' => 'Verify membership payments as accountant (Tier 2)',
|
||||
'verify_payments_chair' => 'Verify membership payments as chair (Tier 3)',
|
||||
'activate_memberships' => 'Activate member accounts after payment approval',
|
||||
'view_payment_verifications' => 'View payment verification dashboard',
|
||||
];
|
||||
|
||||
foreach ($permissions as $name => $description) {
|
||||
Permission::firstOrCreate(
|
||||
['name' => $name],
|
||||
['guard_name' => 'web']
|
||||
);
|
||||
$this->command->info("Permission created: {$name}");
|
||||
}
|
||||
|
||||
// Create roles for payment verification
|
||||
$roles = [
|
||||
'payment_cashier' => [
|
||||
'permissions' => ['verify_payments_cashier', 'view_payment_verifications'],
|
||||
'description' => 'Cashier - First tier payment verification',
|
||||
],
|
||||
'payment_accountant' => [
|
||||
'permissions' => ['verify_payments_accountant', 'view_payment_verifications'],
|
||||
'description' => 'Accountant - Second tier payment verification',
|
||||
],
|
||||
'payment_chair' => [
|
||||
'permissions' => ['verify_payments_chair', 'view_payment_verifications'],
|
||||
'description' => 'Chair - Final tier payment verification',
|
||||
],
|
||||
'membership_manager' => [
|
||||
'permissions' => ['activate_memberships', 'view_payment_verifications'],
|
||||
'description' => 'Membership Manager - Can activate memberships after approval',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($roles as $roleName => $roleData) {
|
||||
$role = Role::firstOrCreate(
|
||||
['name' => $roleName],
|
||||
['guard_name' => 'web']
|
||||
);
|
||||
|
||||
// Assign permissions to role
|
||||
$role->syncPermissions($roleData['permissions']);
|
||||
|
||||
$this->command->info("Role created: {$roleName} with permissions: " . implode(', ', $roleData['permissions']));
|
||||
}
|
||||
|
||||
// Assign all payment verification permissions to admin role (if exists)
|
||||
$adminRole = Role::where('name', 'admin')->first();
|
||||
if ($adminRole) {
|
||||
$adminRole->givePermissionTo([
|
||||
'verify_payments_cashier',
|
||||
'verify_payments_accountant',
|
||||
'verify_payments_chair',
|
||||
'activate_memberships',
|
||||
'view_payment_verifications',
|
||||
]);
|
||||
$this->command->info("Admin role updated with all payment verification permissions");
|
||||
}
|
||||
}
|
||||
}
|
||||
27
database/seeders/RoleSeeder.php
Normal file
27
database/seeders/RoleSeeder.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class RoleSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$roles = [
|
||||
'admin' => 'Full system administrator',
|
||||
'staff' => 'General staff with access to internal tools',
|
||||
'cashier' => 'Handles payment recording and finance intake',
|
||||
'accountant' => 'Reviews finance docs and approvals',
|
||||
'chair' => 'Board chairperson for final approvals',
|
||||
];
|
||||
|
||||
collect($roles)->each(function ($description, $role) {
|
||||
Role::updateOrCreate(
|
||||
['name' => $role, 'guard_name' => 'web'],
|
||||
['description' => $description]
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
240
database/seeders/SystemSettingsSeeder.php
Normal file
240
database/seeders/SystemSettingsSeeder.php
Normal file
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\SystemSetting;
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class SystemSettingsSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$settings = [
|
||||
// General Settings
|
||||
[
|
||||
'key' => 'general.system_name',
|
||||
'value' => 'Usher Management System',
|
||||
'type' => 'string',
|
||||
'group' => 'general',
|
||||
'description' => 'System name displayed throughout the application'
|
||||
],
|
||||
[
|
||||
'key' => 'general.timezone',
|
||||
'value' => 'Asia/Taipei',
|
||||
'type' => 'string',
|
||||
'group' => 'general',
|
||||
'description' => 'System timezone'
|
||||
],
|
||||
|
||||
// Document Features
|
||||
[
|
||||
'key' => 'features.qr_codes_enabled',
|
||||
'value' => '1',
|
||||
'type' => 'boolean',
|
||||
'group' => 'features',
|
||||
'description' => 'Enable QR code generation for documents'
|
||||
],
|
||||
[
|
||||
'key' => 'features.tagging_enabled',
|
||||
'value' => '1',
|
||||
'type' => 'boolean',
|
||||
'group' => 'features',
|
||||
'description' => 'Enable document tagging system'
|
||||
],
|
||||
[
|
||||
'key' => 'features.expiration_enabled',
|
||||
'value' => '1',
|
||||
'type' => 'boolean',
|
||||
'group' => 'features',
|
||||
'description' => 'Enable document expiration dates and auto-archive'
|
||||
],
|
||||
[
|
||||
'key' => 'features.bulk_import_enabled',
|
||||
'value' => '1',
|
||||
'type' => 'boolean',
|
||||
'group' => 'features',
|
||||
'description' => 'Enable bulk document import feature'
|
||||
],
|
||||
[
|
||||
'key' => 'features.statistics_enabled',
|
||||
'value' => '1',
|
||||
'type' => 'boolean',
|
||||
'group' => 'features',
|
||||
'description' => 'Enable document statistics dashboard'
|
||||
],
|
||||
[
|
||||
'key' => 'features.version_history_enabled',
|
||||
'value' => '1',
|
||||
'type' => 'boolean',
|
||||
'group' => 'features',
|
||||
'description' => 'Enable document version history tracking'
|
||||
],
|
||||
|
||||
// Security & Limits
|
||||
[
|
||||
'key' => 'security.rate_limit_authenticated',
|
||||
'value' => '50',
|
||||
'type' => 'integer',
|
||||
'group' => 'security',
|
||||
'description' => 'Downloads per hour for authenticated users'
|
||||
],
|
||||
[
|
||||
'key' => 'security.rate_limit_guest',
|
||||
'value' => '10',
|
||||
'type' => 'integer',
|
||||
'group' => 'security',
|
||||
'description' => 'Downloads per hour for guest users'
|
||||
],
|
||||
[
|
||||
'key' => 'security.max_file_size_mb',
|
||||
'value' => '10',
|
||||
'type' => 'integer',
|
||||
'group' => 'security',
|
||||
'description' => 'Maximum file upload size in MB'
|
||||
],
|
||||
[
|
||||
'key' => 'security.allowed_file_types',
|
||||
'value' => json_encode(['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'jpg', 'jpeg', 'png']),
|
||||
'type' => 'json',
|
||||
'group' => 'security',
|
||||
'description' => 'Allowed file types for uploads'
|
||||
],
|
||||
|
||||
// Document Settings
|
||||
[
|
||||
'key' => 'documents.default_access_level',
|
||||
'value' => 'members',
|
||||
'type' => 'string',
|
||||
'group' => 'documents',
|
||||
'description' => 'Default access level for new documents (public, members, admin, board)'
|
||||
],
|
||||
[
|
||||
'key' => 'documents.default_expiration_days',
|
||||
'value' => '90',
|
||||
'type' => 'integer',
|
||||
'group' => 'documents',
|
||||
'description' => 'Default expiration period in days (0 = no expiration)'
|
||||
],
|
||||
[
|
||||
'key' => 'documents.expiration_warning_days',
|
||||
'value' => '30',
|
||||
'type' => 'integer',
|
||||
'group' => 'documents',
|
||||
'description' => 'Days before expiration to show warning'
|
||||
],
|
||||
[
|
||||
'key' => 'documents.auto_archive_enabled',
|
||||
'value' => '0',
|
||||
'type' => 'boolean',
|
||||
'group' => 'documents',
|
||||
'description' => 'Automatically archive expired documents'
|
||||
],
|
||||
[
|
||||
'key' => 'documents.max_tags_per_document',
|
||||
'value' => '10',
|
||||
'type' => 'integer',
|
||||
'group' => 'documents',
|
||||
'description' => 'Maximum number of tags per document'
|
||||
],
|
||||
|
||||
// Notifications
|
||||
[
|
||||
'key' => 'notifications.enabled',
|
||||
'value' => '1',
|
||||
'type' => 'boolean',
|
||||
'group' => 'notifications',
|
||||
'description' => 'Enable email notifications'
|
||||
],
|
||||
[
|
||||
'key' => 'notifications.expiration_alerts_enabled',
|
||||
'value' => '1',
|
||||
'type' => 'boolean',
|
||||
'group' => 'notifications',
|
||||
'description' => 'Send email alerts for expiring documents'
|
||||
],
|
||||
[
|
||||
'key' => 'notifications.expiration_recipients',
|
||||
'value' => json_encode([]),
|
||||
'type' => 'json',
|
||||
'group' => 'notifications',
|
||||
'description' => 'Email recipients for expiration alerts'
|
||||
],
|
||||
[
|
||||
'key' => 'notifications.archive_notifications_enabled',
|
||||
'value' => '1',
|
||||
'type' => 'boolean',
|
||||
'group' => 'notifications',
|
||||
'description' => 'Send notifications when documents are auto-archived'
|
||||
],
|
||||
[
|
||||
'key' => 'notifications.new_document_alerts_enabled',
|
||||
'value' => '0',
|
||||
'type' => 'boolean',
|
||||
'group' => 'notifications',
|
||||
'description' => 'Send alerts when new documents are uploaded'
|
||||
],
|
||||
|
||||
// Advanced Settings
|
||||
[
|
||||
'key' => 'advanced.qr_code_size',
|
||||
'value' => '300',
|
||||
'type' => 'integer',
|
||||
'group' => 'advanced',
|
||||
'description' => 'QR code size in pixels'
|
||||
],
|
||||
[
|
||||
'key' => 'advanced.qr_code_format',
|
||||
'value' => 'png',
|
||||
'type' => 'string',
|
||||
'group' => 'advanced',
|
||||
'description' => 'QR code format (png or svg)'
|
||||
],
|
||||
[
|
||||
'key' => 'advanced.statistics_time_range',
|
||||
'value' => '30',
|
||||
'type' => 'integer',
|
||||
'group' => 'advanced',
|
||||
'description' => 'Default time range for statistics in days'
|
||||
],
|
||||
[
|
||||
'key' => 'advanced.statistics_top_n',
|
||||
'value' => '10',
|
||||
'type' => 'integer',
|
||||
'group' => 'advanced',
|
||||
'description' => 'Number of top items to display in statistics'
|
||||
],
|
||||
[
|
||||
'key' => 'advanced.audit_log_retention_days',
|
||||
'value' => '365',
|
||||
'type' => 'integer',
|
||||
'group' => 'advanced',
|
||||
'description' => 'How long to retain audit logs in days'
|
||||
],
|
||||
[
|
||||
'key' => 'advanced.max_versions_retain',
|
||||
'value' => '0',
|
||||
'type' => 'integer',
|
||||
'group' => 'advanced',
|
||||
'description' => 'Maximum versions to retain per document (0 = unlimited)'
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($settings as $settingData) {
|
||||
SystemSetting::updateOrCreate(
|
||||
['key' => $settingData['key']],
|
||||
[
|
||||
'value' => $settingData['value'],
|
||||
'type' => $settingData['type'],
|
||||
'group' => $settingData['group'],
|
||||
'description' => $settingData['description'],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$this->command->info('System settings seeded successfully (' . count($settings) . ' settings)');
|
||||
}
|
||||
}
|
||||
769
database/seeders/TestDataSeeder.php
Normal file
769
database/seeders/TestDataSeeder.php
Normal 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('');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user