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,242 @@
<?php
namespace Tests\Feature\Document;
use App\Http\Middleware\VerifyCsrfToken;
use App\Models\Document;
use App\Models\DocumentCategory;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
use Tests\Traits\SeedsRolesAndPermissions;
/**
* Document Tests
*
* Tests document management functionality.
*/
class DocumentTest extends TestCase
{
use RefreshDatabase, SeedsRolesAndPermissions;
protected User $admin;
protected function setUp(): void
{
parent::setUp();
Storage::fake('private');
$this->seedRolesAndPermissions();
$this->admin = $this->createAdmin();
}
/**
* Test can view documents list
*/
public function test_can_view_documents_list(): void
{
$response = $this->actingAs($this->admin)->get(
route('admin.documents.index')
);
$response->assertStatus(200);
}
/**
* Test can upload document
*/
public function test_can_upload_document(): void
{
$category = DocumentCategory::factory()->create();
$file = UploadedFile::fake()->create('test.pdf', 1024);
$response = $this->withoutMiddleware(VerifyCsrfToken::class)
->actingAs($this->admin)
->post(route('admin.documents.store'), [
'title' => '測試文件',
'description' => '這是測試文件',
'document_category_id' => $category->id,
'access_level' => 'admin',
'file' => $file,
]);
$response->assertRedirect();
$this->assertDatabaseHas('documents', ['title' => '測試文件']);
}
/**
* Test can view document details
*/
public function test_can_view_document_details(): void
{
$category = DocumentCategory::factory()->create();
$document = Document::factory()->create([
'created_by_user_id' => $this->admin->id,
'document_category_id' => $category->id,
]);
$response = $this->actingAs($this->admin)->get(
route('admin.documents.show', $document)
);
$response->assertStatus(200);
}
/**
* Test can update document
*/
public function test_can_update_document(): void
{
$category = DocumentCategory::factory()->create();
$document = Document::factory()->create([
'created_by_user_id' => $this->admin->id,
'document_category_id' => $category->id,
'title' => '原始標題',
'access_level' => 'admin',
]);
$response = $this->withoutMiddleware(VerifyCsrfToken::class)
->actingAs($this->admin)
->patch(route('admin.documents.update', $document), [
'title' => '更新後標題',
'document_category_id' => $category->id,
'access_level' => 'admin',
]);
$document->refresh();
$this->assertEquals('更新後標題', $document->title);
}
/**
* Test can delete document
*/
public function test_can_delete_document(): void
{
$category = DocumentCategory::factory()->create();
$document = Document::factory()->create([
'created_by_user_id' => $this->admin->id,
'document_category_id' => $category->id,
]);
$response = $this->withoutMiddleware(VerifyCsrfToken::class)
->actingAs($this->admin)
->delete(route('admin.documents.destroy', $document));
$response->assertRedirect();
$this->assertSoftDeleted('documents', ['id' => $document->id]);
}
/**
* Test document requires title
*/
public function test_document_requires_title(): void
{
$category = DocumentCategory::factory()->create();
$file = UploadedFile::fake()->create('test.pdf', 1024);
$response = $this->withoutMiddleware(VerifyCsrfToken::class)
->actingAs($this->admin)
->post(route('admin.documents.store'), [
'description' => '這是測試文件',
'document_category_id' => $category->id,
'access_level' => 'admin',
'file' => $file,
]);
$response->assertSessionHasErrors('title');
}
/**
* Test document requires category
*/
public function test_document_requires_category(): void
{
$file = UploadedFile::fake()->create('test.pdf', 1024);
$response = $this->withoutMiddleware(VerifyCsrfToken::class)
->actingAs($this->admin)
->post(route('admin.documents.store'), [
'title' => '測試文件',
'description' => '這是測試文件',
'access_level' => 'admin',
'file' => $file,
]);
$response->assertSessionHasErrors('document_category_id');
}
/**
* Test document category filter
*/
public function test_document_category_filter(): void
{
$category1 = DocumentCategory::factory()->create(['name' => '會議紀錄']);
$category2 = DocumentCategory::factory()->create(['name' => '財務報表']);
Document::factory()->create([
'created_by_user_id' => $this->admin->id,
'document_category_id' => $category1->id,
'title' => '文件A',
]);
Document::factory()->create([
'created_by_user_id' => $this->admin->id,
'document_category_id' => $category2->id,
'title' => '文件B',
]);
$response = $this->actingAs($this->admin)->get(
route('admin.documents.index', ['category' => $category1->id])
);
$response->assertStatus(200);
$response->assertSee('文件A');
}
/**
* Test document search
*/
public function test_document_search(): void
{
$category = DocumentCategory::factory()->create();
Document::factory()->create([
'created_by_user_id' => $this->admin->id,
'document_category_id' => $category->id,
'title' => '重要會議紀錄',
]);
$response = $this->actingAs($this->admin)->get(
route('admin.documents.index', ['search' => '重要會議'])
);
$response->assertStatus(200);
$response->assertSee('重要會議紀錄');
}
/**
* Test document version upload
*/
public function test_document_version_upload(): void
{
$category = DocumentCategory::factory()->create();
$document = Document::factory()->create([
'created_by_user_id' => $this->admin->id,
'document_category_id' => $category->id,
'version_count' => 1,
]);
// Upload new version
$file = UploadedFile::fake()->create('test_v2.pdf', 1024);
$response = $this->withoutMiddleware(VerifyCsrfToken::class)
->actingAs($this->admin)
->post(route('admin.documents.upload-version', $document), [
'file' => $file,
'version_notes' => '更新版本說明',
]);
$document->refresh();
$this->assertEquals(2, $document->version_count);
}
}