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

@@ -217,7 +217,7 @@ class AdminMemberController extends Controller
public function showActivate(Member $member)
{
// Check if user has permission
if (!auth()->user()->can('activate_memberships') && !auth()->user()->is_admin) {
if (!auth()->user()->can('activate_memberships') && !auth()->user()->hasRole('admin')) {
abort(403, 'You do not have permission to activate memberships.');
}
@@ -227,7 +227,7 @@ class AdminMemberController extends Controller
->latest()
->first();
if (!$approvedPayment && !auth()->user()->is_admin) {
if (!$approvedPayment && !auth()->user()->hasRole('admin')) {
return redirect()->route('admin.members.show', $member)
->with('error', __('Member must have an approved payment before activation.'));
}
@@ -241,7 +241,7 @@ class AdminMemberController extends Controller
public function activate(Request $request, Member $member)
{
// Check if user has permission
if (!auth()->user()->can('activate_memberships') && !auth()->user()->is_admin) {
if (!auth()->user()->can('activate_memberships') && !auth()->user()->hasRole('admin')) {
abort(403, 'You do not have permission to activate memberships.');
}
@@ -344,4 +344,53 @@ class AdminMemberController extends Controller
return $response;
}
public function batchDestroy(Request $request)
{
$validated = $request->validate([
'ids' => ['required', 'array'],
'ids.*' => ['exists:members,id'],
]);
$count = Member::whereIn('id', $validated['ids'])->delete();
AuditLogger::log('members.batch_deleted', null, ['ids' => $validated['ids'], 'count' => $count]);
return back()->with('status', __(':count members deleted successfully.', ['count' => $count]));
}
public function batchUpdateStatus(Request $request)
{
$validated = $request->validate([
'ids' => ['required', 'array'],
'ids.*' => ['exists:members,id'],
'status' => ['required', 'in:pending,active,expired,suspended'],
]);
$count = Member::whereIn('id', $validated['ids'])->update(['membership_status' => $validated['status']]);
AuditLogger::log('members.batch_status_updated', null, [
'ids' => $validated['ids'],
'status' => $validated['status'],
'count' => $count
]);
return back()->with('status', __(':count members updated successfully.', ['count' => $count]));
}
/**
* View member's disability certificate
*/
public function viewDisabilityCertificate(Member $member)
{
if (!$member->disability_certificate_path) {
abort(404, '找不到身心障礙手冊');
}
if (!\Illuminate\Support\Facades\Storage::disk('private')->exists($member->disability_certificate_path)) {
abort(404, '檔案不存在');
}
return \Illuminate\Support\Facades\Storage::disk('private')->response($member->disability_certificate_path);
}
}