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,167 @@
<?php
namespace Tests\Browser;
use App\Models\Member;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
/**
* Member Registration Browser Tests
*
* Tests the member registration user interface and user experience.
*/
class MemberRegistrationBrowserTest extends DuskTestCase
{
use DatabaseMigrations;
/**
* Test registration page loads correctly
*/
public function test_registration_page_loads_correctly(): void
{
$this->browse(function (Browser $browser) {
$browser->visit(route('register.member'))
->assertSee('會員註冊')
->assertPresent('input[name="full_name"]')
->assertPresent('input[name="email"]')
->assertPresent('input[name="password"]');
});
}
/**
* Test form validation messages display
*/
public function test_form_validation_messages_display(): void
{
$this->browse(function (Browser $browser) {
$browser->visit(route('register.member'))
->press('註冊')
->assertSee('必填');
});
}
/**
* Test successful registration redirects correctly
*/
public function test_successful_registration_redirects(): void
{
$this->artisan('db:seed', ['--class' => 'FinancialWorkflowPermissionsSeeder']);
$this->browse(function (Browser $browser) {
$browser->visit(route('register.member'))
->type('full_name', '測試會員')
->type('email', 'test@example.com')
->type('password', 'password123')
->type('password_confirmation', 'password123')
->type('national_id', 'A123456789')
->type('birthday', '1990-01-01')
->type('phone', '0912345678')
->type('address', '台北市信義區')
->press('註冊')
->waitForLocation('/member/dashboard')
->assertPathIs('/member/dashboard');
});
}
/**
* Test email field validation
*/
public function test_email_field_validation(): void
{
$this->browse(function (Browser $browser) {
$browser->visit(route('register.member'))
->type('email', 'invalid-email')
->press('註冊')
->assertSee('電子郵件');
});
}
/**
* Test password confirmation validation
*/
public function test_password_confirmation_validation(): void
{
$this->browse(function (Browser $browser) {
$browser->visit(route('register.member'))
->type('password', 'password123')
->type('password_confirmation', 'different')
->press('註冊')
->assertSee('密碼');
});
}
/**
* Test national ID format validation
*/
public function test_national_id_format_validation(): void
{
$this->browse(function (Browser $browser) {
$browser->visit(route('register.member'))
->type('national_id', 'invalid')
->press('註冊')
->assertSee('身分證');
});
}
/**
* Test duplicate email detection
*/
public function test_duplicate_email_detection(): void
{
User::factory()->create(['email' => 'existing@example.com']);
$this->browse(function (Browser $browser) {
$browser->visit(route('register.member'))
->type('email', 'existing@example.com')
->press('註冊')
->assertSee('電子郵件');
});
}
/**
* Test file upload for ID photo
*/
public function test_file_upload_for_id_photo(): void
{
$this->browse(function (Browser $browser) {
$browser->visit(route('register.member'))
->assertPresent('input[type="file"]');
});
}
/**
* Test form preserves input on validation error
*/
public function test_form_preserves_input_on_validation_error(): void
{
$this->browse(function (Browser $browser) {
$browser->visit(route('register.member'))
->type('full_name', '測試會員')
->type('email', 'test@example.com')
->press('註冊')
->assertInputValue('full_name', '測試會員')
->assertInputValue('email', 'test@example.com');
});
}
/**
* Test registration form is responsive
*/
public function test_registration_form_is_responsive(): void
{
$this->browse(function (Browser $browser) {
// Test on mobile viewport
$browser->resize(375, 667)
->visit(route('register.member'))
->assertPresent('input[name="full_name"]');
// Test on desktop viewport
$browser->resize(1920, 1080)
->visit(route('register.member'))
->assertPresent('input[name="full_name"]');
});
}
}