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,227 @@
<?php
namespace Tests\Feature\Search;
use App\Models\Document;
use App\Models\FinanceDocument;
use App\Models\Issue;
use App\Models\Member;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
use Tests\Traits\CreatesFinanceData;
use Tests\Traits\CreatesMemberData;
use Tests\Traits\SeedsRolesAndPermissions;
/**
* Search Tests
*
* Tests search functionality across different modules.
*/
class SearchTest extends TestCase
{
use RefreshDatabase, SeedsRolesAndPermissions, CreatesMemberData, CreatesFinanceData;
protected User $admin;
protected function setUp(): void
{
parent::setUp();
Storage::fake('private');
Storage::fake('local');
$this->seedRolesAndPermissions();
$this->admin = $this->createAdmin();
}
/**
* Test member search by name
*/
public function test_member_search_by_name(): void
{
$this->createMember(['full_name' => '張三']);
$this->createMember(['full_name' => '李四']);
$this->createMember(['full_name' => '王五']);
$response = $this->actingAs($this->admin)->get(
route('admin.members.index', ['search' => '張三'])
);
$response->assertStatus(200);
$response->assertSee('張三');
$response->assertDontSee('李四');
}
/**
* Test member search by email
*/
public function test_member_search_by_email(): void
{
$this->createMember(['full_name' => 'Test']);
$response = $this->actingAs($this->admin)->get(
route('admin.members.index', ['search' => 'test'])
);
$response->assertStatus(200);
}
/**
* Test member search by member number
*/
public function test_member_search_by_member_number(): void
{
$member = $this->createMember();
$response = $this->actingAs($this->admin)->get(
route('admin.members.index', ['search' => $member->member_number])
);
$response->assertStatus(200);
}
/**
* Test issue search by title
*/
public function test_issue_search_by_title(): void
{
Issue::factory()->create([
'created_by_user_id' => $this->admin->id,
'title' => '重要議題標題',
]);
Issue::factory()->create([
'created_by_user_id' => $this->admin->id,
'title' => '其他議題',
]);
$response = $this->actingAs($this->admin)->get(
route('admin.issues.index', ['search' => '重要議題'])
);
$response->assertStatus(200);
$response->assertSee('重要議題標題');
}
/**
* Test issue search by issue number
*/
public function test_issue_search_by_issue_number(): void
{
$issue = Issue::factory()->create([
'created_by_user_id' => $this->admin->id,
]);
$response = $this->actingAs($this->admin)->get(
route('admin.issues.index', ['search' => $issue->issue_number])
);
$response->assertStatus(200);
}
/**
* Test finance document search by title
*/
public function test_finance_document_search_by_title(): void
{
$this->createFinanceDocument(['title' => '辦公用品採購']);
$this->createFinanceDocument(['title' => '差旅費報銷']);
$response = $this->actingAs($this->admin)->get(
route('admin.finance-documents.index', ['search' => '辦公用品'])
);
$response->assertStatus(200);
}
/**
* Test finance document search by document number
*/
public function test_finance_document_search_by_document_number(): void
{
$document = $this->createFinanceDocument();
$response = $this->actingAs($this->admin)->get(
route('admin.finance-documents.index', ['search' => $document->document_number])
);
$response->assertStatus(200);
}
/**
* Test search with Chinese characters
*/
public function test_search_with_chinese_characters(): void
{
$this->createMember(['full_name' => '測試中文名稱']);
$response = $this->actingAs($this->admin)->get(
route('admin.members.index', ['search' => '中文'])
);
$response->assertStatus(200);
}
/**
* Test search with special characters
*/
public function test_search_with_special_characters(): void
{
$this->createMember(['full_name' => "O'Connor"]);
$response = $this->actingAs($this->admin)->get(
route('admin.members.index', ['search' => "O'Connor"])
);
$response->assertStatus(200);
}
/**
* Test empty search returns all
*/
public function test_empty_search_returns_all(): void
{
$this->createMember(['full_name' => 'Member 1']);
$this->createMember(['full_name' => 'Member 2']);
$response = $this->actingAs($this->admin)->get(
route('admin.members.index', ['search' => ''])
);
$response->assertStatus(200);
}
/**
* Test search is case insensitive
*/
public function test_search_is_case_insensitive(): void
{
Issue::factory()->create([
'created_by_user_id' => $this->admin->id,
'title' => 'TEST ISSUE',
]);
$response = $this->actingAs($this->admin)->get(
route('admin.issues.index', ['search' => 'test issue'])
);
$response->assertStatus(200);
}
/**
* Test search pagination
*/
public function test_search_pagination(): void
{
// Create many members
for ($i = 0; $i < 30; $i++) {
$this->createMember(['full_name' => "搜尋會員{$i}"]);
}
$response = $this->actingAs($this->admin)->get(
route('admin.members.index', ['search' => '搜尋會員', 'page' => 2])
);
$response->assertStatus(200);
}
}