Files
usher-manage-stack/app/Http/Controllers/Admin/DocumentController.php
Gbanyan 642b879dd4 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>
2025-12-01 09:56:01 +08:00

386 lines
12 KiB
PHP

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\AuditLog;
use App\Models\Document;
use App\Models\DocumentCategory;
use App\Models\DocumentVersion;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class DocumentController extends Controller
{
/**
* Display a listing of documents
*/
public function index(Request $request)
{
$query = Document::with(['category', 'currentVersion', 'createdBy'])
->orderBy('created_at', 'desc');
// Filter by category
if ($request->filled('category')) {
$query->where('document_category_id', $request->category);
}
// Filter by access level
if ($request->filled('access_level')) {
$query->where('access_level', $request->access_level);
}
// Filter by status
if ($request->filled('status')) {
$query->where('status', $request->status);
}
// Search
if ($request->filled('search')) {
$search = $request->search;
$query->where(function($q) use ($search) {
$q->where('title', 'like', "%{$search}%")
->orWhere('document_number', 'like', "%{$search}%")
->orWhere('description', 'like', "%{$search}%");
});
}
$documents = $query->paginate(20);
$categories = DocumentCategory::orderBy('sort_order')->get();
return view('admin.documents.index', compact('documents', 'categories'));
}
/**
* Show the form for creating a new document
*/
public function create()
{
$categories = DocumentCategory::orderBy('sort_order')->get();
return view('admin.documents.create', compact('categories'));
}
/**
* Store a newly created document with initial version
*/
public function store(Request $request)
{
$validated = $request->validate([
'document_category_id' => 'required|exists:document_categories,id',
'title' => 'required|string|max:255',
'document_number' => 'nullable|string|max:255|unique:documents,document_number',
'description' => 'nullable|string',
'access_level' => 'required|in:public,members,admin,board',
'file' => 'required|file|max:10240', // 10MB max
'version_notes' => 'nullable|string',
]);
// Upload file
$file = $request->file('file');
$path = $file->store('documents', 'private');
// Create document
$document = Document::create([
'document_category_id' => $validated['document_category_id'],
'title' => $validated['title'],
'document_number' => $validated['document_number'] ?? null,
'description' => $validated['description'] ?? null,
'access_level' => $validated['access_level'],
'status' => 'active',
'created_by_user_id' => auth()->id(),
'version_count' => 0,
]);
// Add first version
$document->addVersion(
filePath: $path,
originalFilename: $file->getClientOriginalName(),
mimeType: $file->getMimeType(),
fileSize: $file->getSize(),
uploadedBy: auth()->user(),
versionNotes: $validated['version_notes'] ?? '初始版本'
);
// Audit log
AuditLog::create([
'user_id' => auth()->id(),
'action' => 'document.created',
'description' => "建立文件:{$document->title}",
'ip_address' => request()->ip(),
]);
return redirect()
->route('admin.documents.show', $document)
->with('status', '文件已成功建立');
}
/**
* Display the specified document
*/
public function show(Document $document)
{
$document->load(['category', 'versions.uploadedBy', 'createdBy', 'lastUpdatedBy', 'accessLogs.user']);
$versionHistory = $document->getVersionHistory();
return view('admin.documents.show', compact('document', 'versionHistory'));
}
/**
* Show the form for editing the document metadata
*/
public function edit(Document $document)
{
$categories = DocumentCategory::orderBy('sort_order')->get();
return view('admin.documents.edit', compact('document', 'categories'));
}
/**
* Update the document metadata (not the file)
*/
public function update(Request $request, Document $document)
{
$validated = $request->validate([
'document_category_id' => 'required|exists:document_categories,id',
'title' => 'required|string|max:255',
'document_number' => 'nullable|string|max:255|unique:documents,document_number,' . $document->id,
'description' => 'nullable|string',
'access_level' => 'required|in:public,members,admin,board',
]);
$document->update([
...$validated,
'last_updated_by_user_id' => auth()->id(),
]);
// Audit log
AuditLog::create([
'user_id' => auth()->id(),
'action' => 'document.updated',
'description' => "更新文件資訊:{$document->title}",
'ip_address' => request()->ip(),
]);
return redirect()
->route('admin.documents.show', $document)
->with('status', '文件資訊已成功更新');
}
/**
* Upload a new version of the document
*/
public function uploadNewVersion(Request $request, Document $document)
{
$validated = $request->validate([
'file' => 'required|file|max:10240', // 10MB max
'version_notes' => 'required|string',
]);
// Upload file
$file = $request->file('file');
$path = $file->store('documents', 'private');
// Add new version
$version = $document->addVersion(
filePath: $path,
originalFilename: $file->getClientOriginalName(),
mimeType: $file->getMimeType(),
fileSize: $file->getSize(),
uploadedBy: auth()->user(),
versionNotes: $validated['version_notes']
);
// Audit log
AuditLog::create([
'user_id' => auth()->id(),
'action' => 'document.version_uploaded',
'description' => "上傳新版本:{$document->title} (版本 {$version->version_number})",
'ip_address' => request()->ip(),
]);
return back()->with('status', "新版本 {$version->version_number} 已成功上傳");
}
/**
* Promote an old version to current
*/
public function promoteVersion(Document $document, DocumentVersion $version)
{
if ($version->document_id !== $document->id) {
return back()->with('error', '版本不符合');
}
$document->promoteVersion($version, auth()->user());
// Audit log
AuditLog::create([
'user_id' => auth()->id(),
'action' => 'document.version_promoted',
'description' => "提升版本為當前版本:{$document->title} (版本 {$version->version_number})",
'ip_address' => request()->ip(),
]);
return back()->with('status', "版本 {$version->version_number} 已設為當前版本");
}
/**
* Download a specific version
*/
public function downloadVersion(Document $document, DocumentVersion $version)
{
if ($version->document_id !== $document->id) {
abort(404);
}
if (!$version->fileExists()) {
abort(404, '檔案不存在');
}
// Log access
$document->logAccess('download', auth()->user());
return Storage::disk('private')->download(
$version->file_path,
$version->original_filename
);
}
/**
* Archive a document
*/
public function archive(Document $document)
{
$document->archive();
// Audit log
AuditLog::create([
'user_id' => auth()->id(),
'action' => 'document.archived',
'description' => "封存文件:{$document->title}",
'ip_address' => request()->ip(),
]);
return back()->with('status', '文件已封存');
}
/**
* Restore an archived document
*/
public function restore(Document $document)
{
$document->unarchive();
// Audit log
AuditLog::create([
'user_id' => auth()->id(),
'action' => 'document.restored',
'description' => "恢復文件:{$document->title}",
'ip_address' => request()->ip(),
]);
return back()->with('status', '文件已恢復');
}
/**
* Delete a document permanently
*/
public function destroy(Document $document)
{
$title = $document->title;
// Delete all version files
foreach ($document->versions as $version) {
if ($version->fileExists()) {
Storage::disk('private')->delete($version->file_path);
}
}
$document->delete();
// Audit log
AuditLog::create([
'user_id' => auth()->id(),
'action' => 'document.deleted',
'description' => "刪除文件:{$title}",
'ip_address' => request()->ip(),
]);
return redirect()
->route('admin.documents.index')
->with('status', '文件已永久刪除');
}
/**
* Display document statistics dashboard
*/
public function statistics()
{
// Check if statistics feature is enabled
$settings = app(\App\Services\SettingsService::class);
if (!$settings->isFeatureEnabled('statistics')) {
abort(404, '統計功能未啟用');
}
// Check user permission
if (!auth()->user()->can('view_document_statistics')) {
abort(403, '您沒有檢視文件統計的權限');
}
$stats = [
'total_documents' => Document::where('status', 'active')->count(),
'total_versions' => \App\Models\DocumentVersion::count(),
'total_downloads' => Document::sum('download_count'),
'total_views' => Document::sum('view_count'),
'archived_documents' => Document::where('status', 'archived')->count(),
];
// Documents by category
$documentsByCategory = DocumentCategory::withCount(['activeDocuments'])
->orderBy('active_documents_count', 'desc')
->get();
// Most viewed documents
$mostViewed = Document::with(['category', 'currentVersion'])
->where('status', 'active')
->orderBy('view_count', 'desc')
->limit(10)
->get();
// Most downloaded documents
$mostDownloaded = Document::with(['category', 'currentVersion'])
->where('status', 'active')
->orderBy('download_count', 'desc')
->limit(10)
->get();
// Recent activity (last 30 days)
$recentActivity = \App\Models\DocumentAccessLog::with(['user', 'document'])
->where('accessed_at', '>=', now()->subDays(30))
->latest('accessed_at')
->limit(50)
->get();
// Monthly upload trends (last 6 months)
$uploadTrends = Document::selectRaw("strftime('%Y-%m', created_at) as month, COUNT(*) as count")
->where('created_at', '>=', now()->subMonths(6))
->groupBy('month')
->orderBy('month', 'desc')
->get();
// Access level distribution
$accessLevelStats = Document::selectRaw('access_level, COUNT(*) as count')
->where('status', 'active')
->groupBy('access_level')
->get();
return view('admin.documents.statistics', compact(
'stats',
'documentsByCategory',
'mostViewed',
'mostDownloaded',
'recentActivity',
'uploadTrends',
'accessLevelStats'
));
}
}