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:
@@ -31,23 +31,23 @@
|
||||
<div class="flex items-center gap-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
<span>{{ $issue->issue_type_label }}</span>
|
||||
<span>"</span>
|
||||
<span>{{ __('Created by') }} {{ $issue->creator->name }}</span>
|
||||
<span>建立者 {{ $issue->creator->name }}</span>
|
||||
<span>"</span>
|
||||
<span>{{ $issue->created_at->diffForHumans() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 flex gap-2">
|
||||
@if(!$issue->isClosed() || Auth::user()->is_admin)
|
||||
@if(!$issue->isClosed() || Auth::user()->hasRole('admin'))
|
||||
<a href="{{ route('admin.issues.edit', $issue) }}" class="inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600">
|
||||
{{ __('Edit') }}
|
||||
編輯
|
||||
</a>
|
||||
@endif
|
||||
@if(Auth::user()->is_admin)
|
||||
<form method="POST" action="{{ route('admin.issues.destroy', $issue) }}" onsubmit="return confirm('{{ __('Are you sure?') }}')">
|
||||
@if(Auth::user()->hasRole('admin'))
|
||||
<form method="POST" action="{{ route('admin.issues.destroy', $issue) }}" onsubmit="return confirm('您確定嗎?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="inline-flex items-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 dark:bg-red-500 dark:hover:bg-red-400">
|
||||
{{ __('Delete') }}
|
||||
刪除
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
@@ -68,55 +68,55 @@
|
||||
|
||||
<!-- Description -->
|
||||
<div class="prose dark:prose-invert max-w-none mb-6">
|
||||
<h4 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-2">{{ __('Description') }}</h4>
|
||||
<h4 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-2">描述</h4>
|
||||
<div class="text-gray-700 dark:text-gray-300 whitespace-pre-wrap">{{ $issue->description ?: __('No description provided') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Details Grid -->
|
||||
<dl class="grid grid-cols-1 gap-x-4 gap-y-6 sm:grid-cols-2 border-t border-gray-200 dark:border-gray-700 pt-6">
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Assigned To') }}</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">指派對象</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $issue->assignee?->name ?? __('Unassigned') }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Reviewer') }}</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">審查者</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $issue->reviewer?->name ?? __('None') }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Due Date') }}</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">截止日期</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
@if($issue->due_date)
|
||||
<span class="{{ $issue->is_overdue ? 'text-red-600 dark:text-red-400 font-semibold' : '' }}">
|
||||
{{ $issue->due_date->format('Y-m-d') }}
|
||||
@if($issue->is_overdue)
|
||||
({{ __('Overdue by :days days', ['days' => abs($issue->days_until_due)]) }})
|
||||
(逾期 {{ abs($issue->days_until_due) }} 天)
|
||||
@elseif($issue->days_until_due !== null && $issue->days_until_due >= 0)
|
||||
({{ __(':days days left', ['days' => $issue->days_until_due]) }})
|
||||
({{ $issue->days_until_due }} 天剩餘)
|
||||
@endif
|
||||
</span>
|
||||
@else
|
||||
<span class="text-gray-400">{{ __('No due date') }}</span>
|
||||
<span class="text-gray-400">無截止日期</span>
|
||||
@endif
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Time Tracking') }}</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">時間追蹤</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ number_format($issue->actual_hours, 1) }}h
|
||||
@if($issue->estimated_hours)
|
||||
/ {{ number_format($issue->estimated_hours, 1) }}h {{ __('estimated') }}
|
||||
/ {{ number_format($issue->estimated_hours, 1) }}h 預估
|
||||
@endif
|
||||
</dd>
|
||||
</div>
|
||||
@if($issue->member)
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Related Member') }}</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">相關會員</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $issue->member->full_name }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
@if($issue->parentIssue)
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Parent Issue') }}</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">父任務</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
<a href="{{ route('admin.issues.show', $issue->parentIssue) }}" class="text-indigo-600 hover:underline dark:text-indigo-400">
|
||||
{{ $issue->parentIssue->issue_number }} - {{ $issue->parentIssue->title }}
|
||||
@@ -129,7 +129,7 @@
|
||||
<!-- Sub-tasks -->
|
||||
@if($issue->subTasks->count() > 0)
|
||||
<div class="mt-6 border-t border-gray-200 dark:border-gray-700 pt-6">
|
||||
<h4 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">{{ __('Sub-tasks') }} ({{ $issue->subTasks->count() }})</h4>
|
||||
<h4 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">子任務 ({{ $issue->subTasks->count() }})</h4>
|
||||
<ul class="space-y-2">
|
||||
@foreach($issue->subTasks as $subTask)
|
||||
<li class="flex items-center gap-2">
|
||||
@@ -150,21 +150,21 @@
|
||||
<!-- Workflow Actions -->
|
||||
@if(!$issue->isClosed())
|
||||
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Actions') }}</h3>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">操作</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<!-- Update Status -->
|
||||
<form method="POST" action="{{ route('admin.issues.update-status', $issue) }}" class="inline-flex gap-2">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<select name="status" class="rounded-md border-gray-300 text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
|
||||
<option value="new" @selected($issue->status === 'new')>{{ __('New') }}</option>
|
||||
<option value="assigned" @selected($issue->status === 'assigned')>{{ __('Assigned') }}</option>
|
||||
<option value="in_progress" @selected($issue->status === 'in_progress')>{{ __('In Progress') }}</option>
|
||||
<option value="review" @selected($issue->status === 'review')>{{ __('Review') }}</option>
|
||||
<option value="closed" @selected($issue->status === 'closed')>{{ __('Closed') }}</option>
|
||||
<option value="new" @selected($issue->status === 'new')>新</option>
|
||||
<option value="assigned" @selected($issue->status === 'assigned')>已指派</option>
|
||||
<option value="in_progress" @selected($issue->status === 'in_progress')>進行中</option>
|
||||
<option value="review" @selected($issue->status === 'review')>審查</option>
|
||||
<option value="closed" @selected($issue->status === 'closed')>已結案</option>
|
||||
</select>
|
||||
<button type="submit" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400">
|
||||
{{ __('Update Status') }}
|
||||
更新狀態
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -172,13 +172,13 @@
|
||||
<form method="POST" action="{{ route('admin.issues.assign', $issue) }}" class="inline-flex gap-2">
|
||||
@csrf
|
||||
<select name="assigned_to_user_id" class="rounded-md border-gray-300 text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
|
||||
<option value="">{{ __('Unassigned') }}</option>
|
||||
<option value="">未指派</option>
|
||||
@foreach($users as $user)
|
||||
<option value="{{ $user->id }}" @selected($issue->assigned_to_user_id == $user->id)>{{ $user->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<button type="submit" class="inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600">
|
||||
{{ __('Assign') }}
|
||||
指派
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -188,7 +188,7 @@
|
||||
<!-- Comments -->
|
||||
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
|
||||
{{ __('Comments') }} ({{ $issue->comments->count() }})
|
||||
留言 ({{ $issue->comments->count() }})
|
||||
</h3>
|
||||
|
||||
<!-- Comments List -->
|
||||
@@ -199,13 +199,13 @@
|
||||
<span class="font-medium text-sm text-gray-900 dark:text-gray-100">{{ $comment->user->name }}</span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">{{ $comment->created_at->diffForHumans() }}</span>
|
||||
@if($comment->is_internal)
|
||||
<span class="text-xs bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200 px-2 py-0.5 rounded">{{ __('Internal') }}</span>
|
||||
<span class="text-xs bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200 px-2 py-0.5 rounded">內部</span>
|
||||
@endif
|
||||
</div>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300 whitespace-pre-wrap">{{ $comment->comment_text }}</p>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 text-center py-4">{{ __('No comments yet') }}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 text-center py-4">尚無留言</p>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
@@ -214,14 +214,14 @@
|
||||
@csrf
|
||||
<textarea name="comment_text" rows="3" required
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 mb-2"
|
||||
placeholder="{{ __('Add a comment...') }}"></textarea>
|
||||
placeholder="新增留言..."></textarea>
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="inline-flex items-center">
|
||||
<input type="checkbox" name="is_internal" value="1" class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600">
|
||||
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">{{ __('Internal comment') }}</span>
|
||||
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">內部留言</span>
|
||||
</label>
|
||||
<button type="submit" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500">
|
||||
{{ __('Add Comment') }}
|
||||
新增留言
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -230,7 +230,7 @@
|
||||
<!-- Attachments -->
|
||||
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
|
||||
{{ __('Attachments') }} ({{ $issue->attachments->count() }})
|
||||
附件 ({{ $issue->attachments->count() }})
|
||||
</h3>
|
||||
|
||||
<div class="space-y-2 mb-6">
|
||||
@@ -246,18 +246,18 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<a href="{{ route('admin.issues.attachments.download', $attachment) }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 text-sm">{{ __('Download') }}</a>
|
||||
@if(Auth::user()->is_admin)
|
||||
<form method="POST" action="{{ route('admin.issues.attachments.destroy', $attachment) }}" onsubmit="return confirm('{{ __('Delete this attachment?') }}')">
|
||||
<a href="{{ route('admin.issues.attachments.download', $attachment) }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 text-sm">下載</a>
|
||||
@if(Auth::user()->hasRole('admin'))
|
||||
<form method="POST" action="{{ route('admin.issues.attachments.destroy', $attachment) }}" onsubmit="return confirm('刪除此附件?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="text-red-600 hover:text-red-900 dark:text-red-400 text-sm">{{ __('Delete') }}</button>
|
||||
<button type="submit" class="text-red-600 hover:text-red-900 dark:text-red-400 text-sm">刪除</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 text-center py-4">{{ __('No attachments') }}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 text-center py-4">無附件</p>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
@@ -267,17 +267,17 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="file" name="file" required class="block w-full text-sm text-gray-900 dark:text-gray-100 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100 dark:file:bg-indigo-900 dark:file:text-indigo-200">
|
||||
<button type="submit" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500">
|
||||
{{ __('Upload') }}
|
||||
上傳
|
||||
</button>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ __('Max size: 10MB') }}</p>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">最大大小:10MB</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Time Logs -->
|
||||
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
|
||||
{{ __('Time Tracking') }} ({{ number_format($issue->total_time_logged, 1) }}h total)
|
||||
時間追蹤 ({{ number_format($issue->total_time_logged, 1) }}h total)
|
||||
</h3>
|
||||
|
||||
<div class="space-y-2 mb-6">
|
||||
@@ -289,21 +289,21 @@
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 text-center py-4">{{ __('No time logged yet') }}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 text-center py-4">尚未記錄時間</p>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<!-- Log Time Form -->
|
||||
<form method="POST" action="{{ route('admin.issues.time-logs.store', $issue) }}" class="border-t border-gray-200 dark:border-gray-700 pt-4 grid grid-cols-2 gap-2">
|
||||
@csrf
|
||||
<input type="number" name="hours" step="0.25" min="0.25" placeholder="{{ __('Hours') }}" required
|
||||
<input type="number" name="hours" step="0.25" min="0.25" placeholder="時數" required
|
||||
class="rounded-md border-gray-300 text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
|
||||
<input type="date" name="logged_at" value="{{ now()->format('Y-m-d') }}" required
|
||||
class="rounded-md border-gray-300 text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
|
||||
<input type="text" name="description" placeholder="{{ __('What did you do?') }}"
|
||||
<input type="text" name="description" placeholder="您做了什麼?"
|
||||
class="col-span-2 rounded-md border-gray-300 text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
|
||||
<button type="submit" class="col-span-2 inline-flex justify-center items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500">
|
||||
{{ __('Log Time') }}
|
||||
記錄時間
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -313,11 +313,11 @@
|
||||
<div class="space-y-6">
|
||||
<!-- Timeline -->
|
||||
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Progress') }}</h3>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">進度</h3>
|
||||
<x-issue.timeline :issue="$issue" />
|
||||
<div class="mt-4">
|
||||
<div class="flex justify-between text-sm mb-1">
|
||||
<span class="text-gray-700 dark:text-gray-300">{{ __('Completion') }}</span>
|
||||
<span class="text-gray-700 dark:text-gray-300">完成度</span>
|
||||
<span class="font-medium text-gray-900 dark:text-gray-100">{{ $issue->progress_percentage }}%</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||||
@@ -329,7 +329,7 @@
|
||||
<!-- Watchers -->
|
||||
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
|
||||
{{ __('Watchers') }} ({{ $issue->watchers->count() }})
|
||||
觀察者 ({{ $issue->watchers->count() }})
|
||||
</h3>
|
||||
|
||||
<ul class="space-y-2 mb-4">
|
||||
@@ -341,7 +341,7 @@
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<input type="hidden" name="user_id" value="{{ $watcher->id }}">
|
||||
<button type="submit" class="text-xs text-red-600 hover:text-red-900 dark:text-red-400">{{ __('Remove') }}</button>
|
||||
<button type="submit" class="text-xs text-red-600 hover:text-red-900 dark:text-red-400">移除</button>
|
||||
</form>
|
||||
@endif
|
||||
</li>
|
||||
@@ -353,13 +353,13 @@
|
||||
@csrf
|
||||
<div class="flex gap-2">
|
||||
<select name="user_id" required class="flex-1 rounded-md border-gray-300 text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
|
||||
<option value="">{{ __('Add watcher...') }}</option>
|
||||
<option value="">新增觀察者...</option>
|
||||
@foreach($users->whereNotIn('id', $issue->watchers->pluck('id')) as $user)
|
||||
<option value="{{ $user->id }}">{{ $user->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<button type="submit" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500">
|
||||
{{ __('Add') }}
|
||||
新增
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user