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>
271 lines
7.1 KiB
PHP
271 lines
7.1 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\BatchOperations;
|
|
|
|
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;
|
|
|
|
/**
|
|
* Batch Operations Tests
|
|
*
|
|
* Tests bulk operations on records.
|
|
*/
|
|
class BatchOperationsTest 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 batch member status update
|
|
*/
|
|
public function test_batch_member_status_update(): void
|
|
{
|
|
$members = [];
|
|
for ($i = 0; $i < 5; $i++) {
|
|
$members[] = $this->createPendingMember();
|
|
}
|
|
|
|
$memberIds = array_map(fn ($m) => $m->id, $members);
|
|
|
|
$response = $this->actingAs($this->admin)->post(
|
|
route('admin.members.batch-update'),
|
|
[
|
|
'member_ids' => $memberIds,
|
|
'action' => 'activate',
|
|
]
|
|
);
|
|
|
|
foreach ($members as $member) {
|
|
$member->refresh();
|
|
$this->assertEquals(Member::STATUS_ACTIVE, $member->membership_status);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test batch member suspend
|
|
*/
|
|
public function test_batch_member_suspend(): void
|
|
{
|
|
$members = [];
|
|
for ($i = 0; $i < 3; $i++) {
|
|
$members[] = $this->createActiveMember();
|
|
}
|
|
|
|
$memberIds = array_map(fn ($m) => $m->id, $members);
|
|
|
|
$response = $this->actingAs($this->admin)->post(
|
|
route('admin.members.batch-update'),
|
|
[
|
|
'member_ids' => $memberIds,
|
|
'action' => 'suspend',
|
|
]
|
|
);
|
|
|
|
foreach ($members as $member) {
|
|
$member->refresh();
|
|
$this->assertEquals(Member::STATUS_SUSPENDED, $member->membership_status);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test batch issue status update
|
|
*/
|
|
public function test_batch_issue_status_update(): void
|
|
{
|
|
$issues = [];
|
|
for ($i = 0; $i < 5; $i++) {
|
|
$issues[] = Issue::factory()->create([
|
|
'created_by_user_id' => $this->admin->id,
|
|
'status' => Issue::STATUS_NEW,
|
|
]);
|
|
}
|
|
|
|
$issueIds = array_map(fn ($i) => $i->id, $issues);
|
|
|
|
$response = $this->actingAs($this->admin)->post(
|
|
route('admin.issues.batch-update'),
|
|
[
|
|
'issue_ids' => $issueIds,
|
|
'status' => Issue::STATUS_IN_PROGRESS,
|
|
]
|
|
);
|
|
|
|
foreach ($issues as $issue) {
|
|
$issue->refresh();
|
|
$this->assertEquals(Issue::STATUS_IN_PROGRESS, $issue->status);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test batch issue assign
|
|
*/
|
|
public function test_batch_issue_assign(): void
|
|
{
|
|
$assignee = User::factory()->create();
|
|
|
|
$issues = [];
|
|
for ($i = 0; $i < 3; $i++) {
|
|
$issues[] = Issue::factory()->create([
|
|
'created_by_user_id' => $this->admin->id,
|
|
'assignee_id' => null,
|
|
]);
|
|
}
|
|
|
|
$issueIds = array_map(fn ($i) => $i->id, $issues);
|
|
|
|
$response = $this->actingAs($this->admin)->post(
|
|
route('admin.issues.batch-assign'),
|
|
[
|
|
'issue_ids' => $issueIds,
|
|
'assignee_id' => $assignee->id,
|
|
]
|
|
);
|
|
|
|
foreach ($issues as $issue) {
|
|
$issue->refresh();
|
|
$this->assertEquals($assignee->id, $issue->assignee_id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test batch issue close
|
|
*/
|
|
public function test_batch_issue_close(): void
|
|
{
|
|
$issues = [];
|
|
for ($i = 0; $i < 3; $i++) {
|
|
$issues[] = Issue::factory()->create([
|
|
'created_by_user_id' => $this->admin->id,
|
|
'status' => Issue::STATUS_REVIEW,
|
|
]);
|
|
}
|
|
|
|
$issueIds = array_map(fn ($i) => $i->id, $issues);
|
|
|
|
$response = $this->actingAs($this->admin)->post(
|
|
route('admin.issues.batch-update'),
|
|
[
|
|
'issue_ids' => $issueIds,
|
|
'status' => Issue::STATUS_CLOSED,
|
|
]
|
|
);
|
|
|
|
foreach ($issues as $issue) {
|
|
$issue->refresh();
|
|
$this->assertEquals(Issue::STATUS_CLOSED, $issue->status);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test batch operation with empty selection
|
|
*/
|
|
public function test_batch_operation_with_empty_selection(): void
|
|
{
|
|
$response = $this->actingAs($this->admin)->post(
|
|
route('admin.members.batch-update'),
|
|
[
|
|
'member_ids' => [],
|
|
'action' => 'activate',
|
|
]
|
|
);
|
|
|
|
$response->assertSessionHasErrors('member_ids');
|
|
}
|
|
|
|
/**
|
|
* Test batch operation with invalid IDs
|
|
*/
|
|
public function test_batch_operation_with_invalid_ids(): void
|
|
{
|
|
$response = $this->actingAs($this->admin)->post(
|
|
route('admin.members.batch-update'),
|
|
[
|
|
'member_ids' => [99999, 99998],
|
|
'action' => 'activate',
|
|
]
|
|
);
|
|
|
|
// Should handle gracefully
|
|
$response->assertRedirect();
|
|
}
|
|
|
|
/**
|
|
* Test batch export members
|
|
*/
|
|
public function test_batch_export_members(): void
|
|
{
|
|
for ($i = 0; $i < 5; $i++) {
|
|
$this->createActiveMember();
|
|
}
|
|
|
|
$response = $this->actingAs($this->admin)->get(
|
|
route('admin.members.export', ['format' => 'csv'])
|
|
);
|
|
|
|
$response->assertStatus(200);
|
|
$response->assertHeader('content-type', 'text/csv; charset=UTF-8');
|
|
}
|
|
|
|
/**
|
|
* Test batch operation requires permission
|
|
*/
|
|
public function test_batch_operation_requires_permission(): void
|
|
{
|
|
$regularUser = User::factory()->create();
|
|
$member = $this->createPendingMember();
|
|
|
|
$response = $this->actingAs($regularUser)->post(
|
|
route('admin.members.batch-update'),
|
|
[
|
|
'member_ids' => [$member->id],
|
|
'action' => 'activate',
|
|
]
|
|
);
|
|
|
|
$response->assertForbidden();
|
|
}
|
|
|
|
/**
|
|
* Test batch operation limit
|
|
*/
|
|
public function test_batch_operation_limit(): void
|
|
{
|
|
// Create many members
|
|
$members = [];
|
|
for ($i = 0; $i < 100; $i++) {
|
|
$members[] = $this->createPendingMember();
|
|
}
|
|
|
|
$memberIds = array_map(fn ($m) => $m->id, $members);
|
|
|
|
$response = $this->actingAs($this->admin)->post(
|
|
route('admin.members.batch-update'),
|
|
[
|
|
'member_ids' => $memberIds,
|
|
'action' => 'activate',
|
|
]
|
|
);
|
|
|
|
// Should handle large batch
|
|
$this->assertTrue($response->isRedirect() || $response->isSuccessful());
|
|
}
|
|
}
|