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

@@ -0,0 +1,270 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
總分類帳
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8 space-y-6">
<!-- Filter Form -->
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<form method="GET" action="{{ route('admin.general-ledger.index') }}" class="space-y-4">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-4">
<!-- Account Selection -->
<div class="sm:col-span-2">
<label for="account_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
科目
</label>
<select name="account_id" id="account_id"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-gray-100">
<option value="">請選擇科目</option>
@foreach($accounts as $account)
<option value="{{ $account->id }}" @selected(request('account_id') == $account->id)>
{{ $account->account_code }} - {{ $account->account_name_zh }}
</option>
@endforeach
</select>
</div>
<!-- Start Date -->
<div>
<label for="start_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
開始日期
</label>
<input type="date" name="start_date" id="start_date" value="{{ $startDate }}"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-gray-100">
</div>
<!-- End Date -->
<div>
<label for="end_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
結束日期
</label>
<input type="date" name="end_date" id="end_date" value="{{ $endDate }}"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-gray-100">
</div>
</div>
<div class="flex gap-4">
<button type="submit" class="inline-flex justify-center rounded-md bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
查詢
</button>
<a href="{{ route('admin.general-ledger.index') }}" class="inline-flex justify-center rounded-md bg-white dark:bg-gray-700 px-4 py-2 text-sm font-semibold text-gray-900 dark:text-gray-100 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600">
清除
</a>
</div>
</form>
</div>
</div>
@if($selectedAccount)
<!-- Account Info -->
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">
{{ $selectedAccount->account_code }} - {{ $selectedAccount->account_name_zh }}
<span class="text-sm text-gray-500 dark:text-gray-400">
({{ $selectedAccount->account_name_en }})
</span>
</h3>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
類型:
@switch($selectedAccount->account_type)
@case('asset') 資產 @break
@case('liability') 負債 @break
@case('net_asset') 淨資產/基金 @break
@case('income') 收入 @break
@case('expense') 支出 @break
@default {{ $selectedAccount->account_type }}
@endswitch
</p>
</div>
</div>
<!-- Summary Cards -->
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
<!-- Opening Balance -->
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg border-l-4 border-gray-400">
<div class="px-4 py-5 sm:p-6">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
期初餘額
</dt>
<dd class="mt-1 text-2xl font-semibold {{ $openingBalance >= 0 ? 'text-gray-900 dark:text-gray-100' : 'text-red-600 dark:text-red-400' }}">
NT$ {{ number_format($openingBalance, 2) }}
</dd>
</div>
</div>
<!-- Debit Total -->
<div class="bg-blue-50 dark:bg-blue-900/30 shadow sm:rounded-lg border-l-4 border-blue-400">
<div class="px-4 py-5 sm:p-6">
<dt class="text-sm font-medium text-blue-800 dark:text-blue-200">
借方總計
</dt>
<dd class="mt-1 text-2xl font-semibold text-blue-900 dark:text-blue-100">
NT$ {{ number_format($debitTotal, 2) }}
</dd>
</div>
</div>
<!-- Credit Total -->
<div class="bg-purple-50 dark:bg-purple-900/30 shadow sm:rounded-lg border-l-4 border-purple-400">
<div class="px-4 py-5 sm:p-6">
<dt class="text-sm font-medium text-purple-800 dark:text-purple-200">
貸方總計
</dt>
<dd class="mt-1 text-2xl font-semibold text-purple-900 dark:text-purple-100">
NT$ {{ number_format($creditTotal, 2) }}
</dd>
</div>
</div>
<!-- Closing Balance -->
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg border-l-4 {{ $closingBalance >= 0 ? 'border-green-400' : 'border-red-400' }}">
<div class="px-4 py-5 sm:p-6">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
期末餘額
</dt>
<dd class="mt-1 text-2xl font-semibold {{ $closingBalance >= 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400' }}">
NT$ {{ number_format($closingBalance, 2) }}
</dd>
</div>
</div>
</div>
<!-- Entries Table -->
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
分錄明細
</h4>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
日期
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
憑證
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
說明
</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-blue-600 dark:text-blue-400">
借方
</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-purple-600 dark:text-purple-400">
貸方
</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
餘額
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-gray-800">
<!-- Opening Balance Row -->
<tr class="bg-gray-50 dark:bg-gray-700">
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
{{ \Carbon\Carbon::parse($startDate)->format('Y/m/d') }}
</td>
<td class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400" colspan="2">
<em>期初餘額</em>
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400">
-
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400">
-
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-right font-medium {{ $openingBalance >= 0 ? 'text-gray-900 dark:text-gray-100' : 'text-red-600 dark:text-red-400' }}">
{{ number_format($openingBalance, 2) }}
</td>
</tr>
@php $runningBalance = $openingBalance; @endphp
@forelse($entries as $entry)
@php
if (in_array($selectedAccount->account_type, ['asset', 'expense'])) {
$runningBalance += ($entry->entry_type === 'debit' ? $entry->amount : -$entry->amount);
} else {
$runningBalance += ($entry->entry_type === 'credit' ? $entry->amount : -$entry->amount);
}
@endphp
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
{{ $entry->entry_date->format('Y/m/d') }}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm">
@if($entry->financeDocument)
<a href="{{ route('admin.finance.show', $entry->financeDocument) }}" class="text-indigo-600 dark:text-indigo-400 hover:underline">
#{{ $entry->finance_document_id }}
</a>
@else
<span class="text-gray-400">-</span>
@endif
</td>
<td class="px-4 py-3 text-sm text-gray-900 dark:text-gray-100 max-w-xs truncate">
{{ $entry->description ?? '-' }}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-right text-blue-600 dark:text-blue-400">
{{ $entry->entry_type === 'debit' ? number_format($entry->amount, 2) : '' }}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-right text-purple-600 dark:text-purple-400">
{{ $entry->entry_type === 'credit' ? number_format($entry->amount, 2) : '' }}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-right font-medium {{ $runningBalance >= 0 ? 'text-gray-900 dark:text-gray-100' : 'text-red-600 dark:text-red-400' }}">
{{ number_format($runningBalance, 2) }}
</td>
</tr>
@empty
<tr>
<td colspan="6" class="px-4 py-8 text-center text-sm text-gray-500 dark:text-gray-400">
此期間無分錄
</td>
</tr>
@endforelse
<!-- Totals Row -->
@if($entries && $entries->count() > 0)
<tr class="bg-gray-100 dark:bg-gray-700 font-semibold">
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-gray-100" colspan="3">
本期合計
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-right text-blue-700 dark:text-blue-300">
{{ number_format($debitTotal, 2) }}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-right text-purple-700 dark:text-purple-300">
{{ number_format($creditTotal, 2) }}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-right {{ $closingBalance >= 0 ? 'text-green-700 dark:text-green-300' : 'text-red-700 dark:text-red-300' }}">
{{ number_format($closingBalance, 2) }}
</td>
</tr>
@endif
</tbody>
</table>
</div>
</div>
</div>
@else
<!-- No Account Selected -->
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
<div class="px-4 py-12 text-center">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-gray-100">
請選擇科目
</h3>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
從上方下拉選單選擇科目以查看分錄明細
</p>
</div>
</div>
@endif
</div>
</div>
</x-app-layout>