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:
216
tests/Feature/Budget/BudgetTest.php
Normal file
216
tests/Feature/Budget/BudgetTest.php
Normal file
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Budget;
|
||||
|
||||
use App\Models\Budget;
|
||||
use App\Models\BudgetCategory;
|
||||
use App\Models\FinanceDocument;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Tests\TestCase;
|
||||
use Tests\Traits\CreatesFinanceData;
|
||||
use Tests\Traits\SeedsRolesAndPermissions;
|
||||
|
||||
/**
|
||||
* Budget Tests
|
||||
*
|
||||
* Tests budget management and tracking.
|
||||
*/
|
||||
class BudgetTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase, SeedsRolesAndPermissions, CreatesFinanceData;
|
||||
|
||||
protected User $admin;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Storage::fake('local');
|
||||
$this->seedRolesAndPermissions();
|
||||
$this->admin = $this->createAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can view budget dashboard
|
||||
*/
|
||||
public function test_can_view_budget_dashboard(): void
|
||||
{
|
||||
$response = $this->actingAs($this->admin)->get(
|
||||
route('admin.budgets.index')
|
||||
);
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can create budget category
|
||||
*/
|
||||
public function test_can_create_budget_category(): void
|
||||
{
|
||||
$response = $this->actingAs($this->admin)->post(
|
||||
route('admin.budget-categories.store'),
|
||||
[
|
||||
'name' => '行政費用',
|
||||
'description' => '日常行政支出',
|
||||
]
|
||||
);
|
||||
|
||||
$response->assertRedirect();
|
||||
$this->assertDatabaseHas('budget_categories', ['name' => '行政費用']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can create budget
|
||||
*/
|
||||
public function test_can_create_budget(): void
|
||||
{
|
||||
$category = BudgetCategory::factory()->create();
|
||||
|
||||
$response = $this->actingAs($this->admin)->post(
|
||||
route('admin.budgets.store'),
|
||||
[
|
||||
'category_id' => $category->id,
|
||||
'year' => now()->year,
|
||||
'amount' => 100000,
|
||||
'description' => '年度預算',
|
||||
]
|
||||
);
|
||||
|
||||
$response->assertRedirect();
|
||||
$this->assertDatabaseHas('budgets', [
|
||||
'category_id' => $category->id,
|
||||
'amount' => 100000,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test budget tracks spending
|
||||
*/
|
||||
public function test_budget_tracks_spending(): void
|
||||
{
|
||||
$category = BudgetCategory::factory()->create();
|
||||
$budget = Budget::factory()->create([
|
||||
'category_id' => $category->id,
|
||||
'amount' => 100000,
|
||||
'spent' => 0,
|
||||
]);
|
||||
|
||||
// Create finance document linked to category
|
||||
$document = $this->createFinanceDocument([
|
||||
'budget_category_id' => $category->id,
|
||||
'amount' => 5000,
|
||||
'status' => FinanceDocument::STATUS_APPROVED_CHAIR,
|
||||
]);
|
||||
|
||||
// Spending should be updated
|
||||
$budget->refresh();
|
||||
$this->assertGreaterThanOrEqual(0, $budget->spent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test budget overspend warning
|
||||
*/
|
||||
public function test_budget_overspend_warning(): void
|
||||
{
|
||||
$category = BudgetCategory::factory()->create();
|
||||
$budget = Budget::factory()->create([
|
||||
'category_id' => $category->id,
|
||||
'amount' => 10000,
|
||||
'spent' => 9500,
|
||||
]);
|
||||
|
||||
// Budget is 95% used
|
||||
$percentUsed = ($budget->spent / $budget->amount) * 100;
|
||||
$this->assertGreaterThan(90, $percentUsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can update budget amount
|
||||
*/
|
||||
public function test_can_update_budget_amount(): void
|
||||
{
|
||||
$budget = Budget::factory()->create(['amount' => 50000]);
|
||||
|
||||
$response = $this->actingAs($this->admin)->patch(
|
||||
route('admin.budgets.update', $budget),
|
||||
['amount' => 75000]
|
||||
);
|
||||
|
||||
$budget->refresh();
|
||||
$this->assertEquals(75000, $budget->amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test budget year filter
|
||||
*/
|
||||
public function test_budget_year_filter(): void
|
||||
{
|
||||
Budget::factory()->create(['year' => 2024]);
|
||||
Budget::factory()->create(['year' => 2025]);
|
||||
|
||||
$response = $this->actingAs($this->admin)->get(
|
||||
route('admin.budgets.index', ['year' => 2024])
|
||||
);
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test budget category filter
|
||||
*/
|
||||
public function test_budget_category_filter(): void
|
||||
{
|
||||
$category1 = BudgetCategory::factory()->create(['name' => '類別A']);
|
||||
$category2 = BudgetCategory::factory()->create(['name' => '類別B']);
|
||||
|
||||
Budget::factory()->create(['category_id' => $category1->id]);
|
||||
Budget::factory()->create(['category_id' => $category2->id]);
|
||||
|
||||
$response = $this->actingAs($this->admin)->get(
|
||||
route('admin.budgets.index', ['category_id' => $category1->id])
|
||||
);
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test budget remaining calculation
|
||||
*/
|
||||
public function test_budget_remaining_calculation(): void
|
||||
{
|
||||
$budget = Budget::factory()->create([
|
||||
'amount' => 100000,
|
||||
'spent' => 30000,
|
||||
]);
|
||||
|
||||
$remaining = $budget->amount - $budget->spent;
|
||||
$this->assertEquals(70000, $remaining);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test duplicate budget prevention
|
||||
*/
|
||||
public function test_duplicate_budget_prevention(): void
|
||||
{
|
||||
$category = BudgetCategory::factory()->create();
|
||||
$year = now()->year;
|
||||
|
||||
Budget::factory()->create([
|
||||
'category_id' => $category->id,
|
||||
'year' => $year,
|
||||
]);
|
||||
|
||||
// Attempt to create duplicate
|
||||
$response = $this->actingAs($this->admin)->post(
|
||||
route('admin.budgets.store'),
|
||||
[
|
||||
'category_id' => $category->id,
|
||||
'year' => $year,
|
||||
'amount' => 50000,
|
||||
]
|
||||
);
|
||||
|
||||
$response->assertSessionHasErrors();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user