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:
2025-12-01 09:56:01 +08:00
parent 83ce1f7fc8
commit 642b879dd4
207 changed files with 19487 additions and 3048 deletions

View File

@@ -22,7 +22,9 @@ use App\Http\Controllers\PaymentVerificationController;
use App\Http\Controllers\PublicDocumentController;
use App\Http\Controllers\Admin\DocumentController;
use App\Http\Controllers\Admin\DocumentCategoryController;
use App\Http\Controllers\Admin\AnnouncementController;
use App\Http\Controllers\PublicBugReportController;
use App\Http\Controllers\IncomeController;
use Illuminate\Support\Facades\Route;
/*
@@ -60,7 +62,16 @@ Route::get('/dashboard', function () {
->get()
->filter(fn($doc) => $doc->canBeViewedBy(auth()->user()));
return view('dashboard', compact('recentDocuments'));
$recentAnnouncements = \App\Models\Announcement::query()
->published()
->active()
->forAccessLevel(auth()->user())
->orderByDesc('is_pinned')
->orderByDesc('published_at')
->limit(5)
->get();
return view('dashboard', compact('recentDocuments', 'recentAnnouncements'));
})->middleware(['auth', 'verified'])->name('dashboard');
// Public Member Registration Routes
@@ -92,12 +103,18 @@ Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
// Disability Certificate Routes
Route::post('/profile/disability-certificate', [ProfileController::class, 'uploadDisabilityCertificate'])->name('profile.disability-certificate.upload');
Route::get('/profile/disability-certificate', [ProfileController::class, 'viewDisabilityCertificate'])->name('profile.disability-certificate.view');
});
Route::middleware(['auth', 'admin'])->prefix('admin')->name('admin.')->group(function () {
Route::get('/dashboard', [AdminDashboardController::class, 'index'])->name('dashboard');
Route::get('/members', [AdminMemberController::class, 'index'])->name('members.index');
Route::post('/members/batch-destroy', [AdminMemberController::class, 'batchDestroy'])->name('members.batch-destroy');
Route::post('/members/batch-update-status', [AdminMemberController::class, 'batchUpdateStatus'])->name('members.batch-update-status');
Route::get('/members/create', [AdminMemberController::class, 'create'])->name('members.create');
Route::post('/members', [AdminMemberController::class, 'store'])->name('members.store');
Route::get('/members/import', [AdminMemberController::class, 'importForm'])->name('members.import-form');
@@ -122,6 +139,9 @@ Route::middleware(['auth', 'admin'])->prefix('admin')->name('admin.')->group(fun
Route::post('/finance-documents/{financeDocument}/approve', [FinanceDocumentController::class, 'approve'])->name('finance.approve');
Route::post('/finance-documents/{financeDocument}/reject', [FinanceDocumentController::class, 'reject'])->name('finance.reject');
Route::get('/finance-documents/{financeDocument}/download', [FinanceDocumentController::class, 'download'])->name('finance.download');
// 新工作流程:出帳確認與入帳確認
Route::post('/finance-documents/{financeDocument}/confirm-disbursement', [FinanceDocumentController::class, 'confirmDisbursement'])->name('finance.confirm-disbursement');
Route::post('/finance-documents/{financeDocument}/confirm-recording', [FinanceDocumentController::class, 'confirmRecording'])->name('finance.confirm-recording');
// Payment Orders (Stage 2: Payment)
Route::get('/payment-orders', [PaymentOrderController::class, 'index'])->name('payment-orders.index');
@@ -152,6 +172,17 @@ Route::middleware(['auth', 'admin'])->prefix('admin')->name('admin.')->group(fun
Route::get('/bank-reconciliations/{bankReconciliation}/export-pdf', [BankReconciliationController::class, 'exportPdf'])->name('bank-reconciliations.export-pdf');
Route::get('/bank-reconciliations/{bankReconciliation}/pdf', [BankReconciliationController::class, 'exportPdf'])->name('bank-reconciliations.pdf');
// Income Management (收入管理)
Route::get('/incomes', [IncomeController::class, 'index'])->name('incomes.index');
Route::get('/incomes/create', [IncomeController::class, 'create'])->name('incomes.create');
Route::get('/incomes/statistics', [IncomeController::class, 'statistics'])->name('incomes.statistics');
Route::get('/incomes/export', [IncomeController::class, 'export'])->name('incomes.export');
Route::post('/incomes', [IncomeController::class, 'store'])->name('incomes.store');
Route::get('/incomes/{income}', [IncomeController::class, 'show'])->name('incomes.show');
Route::get('/incomes/{income}/download', [IncomeController::class, 'download'])->name('incomes.download');
Route::post('/incomes/{income}/confirm', [IncomeController::class, 'confirm'])->name('incomes.confirm');
Route::post('/incomes/{income}/cancel', [IncomeController::class, 'cancel'])->name('incomes.cancel');
Route::get('/audit-logs', [AdminAuditLogController::class, 'index'])->name('audit.index');
Route::get('/audit-logs/export', [AdminAuditLogController::class, 'export'])->name('audit.export');
@@ -184,6 +215,10 @@ Route::middleware(['auth', 'admin'])->prefix('admin')->name('admin.')->group(fun
Route::patch('/transactions/{transaction}', [TransactionController::class, 'update'])->name('transactions.update');
Route::delete('/transactions/{transaction}', [TransactionController::class, 'destroy'])->name('transactions.destroy');
// General Ledger & Trial Balance Routes
Route::get('/general-ledger', [\App\Http\Controllers\Admin\GeneralLedgerController::class, 'index'])->name('general-ledger.index');
Route::get('/trial-balance', [\App\Http\Controllers\Admin\TrialBalanceController::class, 'index'])->name('trial-balance.index');
// Issue Tracker Routes
Route::get('/issues', [IssueController::class, 'index'])->name('issues.index');
Route::get('/issues/create', [IssueController::class, 'create'])->name('issues.create');
@@ -233,6 +268,7 @@ Route::middleware(['auth', 'admin'])->prefix('admin')->name('admin.')->group(fun
// Membership Activation Routes
Route::get('/members/{member}/activate', [AdminMemberController::class, 'showActivate'])->name('members.activate');
Route::post('/members/{member}/activate', [AdminMemberController::class, 'activate'])->name('members.activate.store');
Route::get('/members/{member}/disability-certificate', [AdminMemberController::class, 'viewDisabilityCertificate'])->name('members.disability-certificate');
// Document Categories Management
Route::get('/document-categories', [DocumentCategoryController::class, 'index'])->name('document-categories.index');
@@ -263,6 +299,21 @@ Route::middleware(['auth', 'admin'])->prefix('admin')->name('admin.')->group(fun
Route::post('/documents/{document}/archive', [DocumentController::class, 'archive'])->name('documents.archive');
Route::post('/documents/{document}/restore', [DocumentController::class, 'restore'])->name('documents.restore');
// Announcement Management
Route::get('/announcements', [AnnouncementController::class, 'index'])->name('announcements.index');
Route::get('/announcements/create', [AnnouncementController::class, 'create'])->name('announcements.create');
Route::post('/announcements', [AnnouncementController::class, 'store'])->name('announcements.store');
Route::get('/announcements/{announcement}', [AnnouncementController::class, 'show'])->name('announcements.show');
Route::get('/announcements/{announcement}/edit', [AnnouncementController::class, 'edit'])->name('announcements.edit');
Route::patch('/announcements/{announcement}', [AnnouncementController::class, 'update'])->name('announcements.update');
Route::delete('/announcements/{announcement}', [AnnouncementController::class, 'destroy'])->name('announcements.destroy');
// Announcement Actions
Route::post('/announcements/{announcement}/publish', [AnnouncementController::class, 'publish'])->name('announcements.publish');
Route::post('/announcements/{announcement}/archive', [AnnouncementController::class, 'archive'])->name('announcements.archive');
Route::post('/announcements/{announcement}/pin', [AnnouncementController::class, 'pin'])->name('announcements.pin');
Route::post('/announcements/{announcement}/unpin', [AnnouncementController::class, 'unpin'])->name('announcements.unpin');
// System Settings (requires manage_system_settings permission)
Route::middleware('can:manage_system_settings')->prefix('settings')->name('settings.')->group(function () {
Route::get('/', [\App\Http\Controllers\Admin\SystemSettingsController::class, 'index'])->name('index');
@@ -281,6 +332,9 @@ Route::middleware(['auth', 'admin'])->prefix('admin')->name('admin.')->group(fun
Route::get('/advanced', [\App\Http\Controllers\Admin\SystemSettingsController::class, 'advanced'])->name('advanced');
Route::post('/advanced', [\App\Http\Controllers\Admin\SystemSettingsController::class, 'updateAdvanced'])->name('advanced.update');
Route::get('/membership', [\App\Http\Controllers\Admin\SystemSettingsController::class, 'membership'])->name('membership');
Route::post('/membership', [\App\Http\Controllers\Admin\SystemSettingsController::class, 'updateMembership'])->name('membership.update');
});
});