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:
259
tests/Feature/CashierLedger/CashierLedgerTest.php
Normal file
259
tests/Feature/CashierLedger/CashierLedgerTest.php
Normal file
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\CashierLedger;
|
||||
|
||||
use App\Models\CashierLedger;
|
||||
use App\Models\PaymentOrder;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Cashier Ledger Tests
|
||||
*
|
||||
* Tests cashier ledger entries in the 4-stage finance workflow.
|
||||
*/
|
||||
class CashierLedgerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase, SeedsRolesAndPermissions, CreatesFinanceData;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Storage::fake('local');
|
||||
$this->seedRolesAndPermissions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can view cashier ledger
|
||||
*/
|
||||
public function test_can_view_cashier_ledger(): void
|
||||
{
|
||||
$cashier = $this->createCashier();
|
||||
|
||||
$response = $this->actingAs($cashier)->get(
|
||||
route('admin.cashier-ledger.index')
|
||||
);
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ledger entry created from payment order
|
||||
*/
|
||||
public function test_ledger_entry_created_from_payment_order(): void
|
||||
{
|
||||
$cashier = $this->createCashier();
|
||||
$order = $this->createPaymentOrder([
|
||||
'status' => PaymentOrder::STATUS_COMPLETED,
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($cashier)->post(
|
||||
route('admin.cashier-ledger.store'),
|
||||
[
|
||||
'payment_order_id' => $order->id,
|
||||
'entry_type' => 'expense',
|
||||
'entry_date' => now()->toDateString(),
|
||||
]
|
||||
);
|
||||
|
||||
$response->assertRedirect();
|
||||
$this->assertDatabaseHas('cashier_ledgers', [
|
||||
'payment_order_id' => $order->id,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ledger tracks income entries
|
||||
*/
|
||||
public function test_ledger_tracks_income_entries(): void
|
||||
{
|
||||
$cashier = $this->createCashier();
|
||||
|
||||
$response = $this->actingAs($cashier)->post(
|
||||
route('admin.cashier-ledger.store'),
|
||||
[
|
||||
'entry_type' => 'income',
|
||||
'amount' => 50000,
|
||||
'description' => '會員繳費收入',
|
||||
'entry_date' => now()->toDateString(),
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertDatabaseHas('cashier_ledgers', [
|
||||
'entry_type' => 'income',
|
||||
'amount' => 50000,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ledger tracks expense entries
|
||||
*/
|
||||
public function test_ledger_tracks_expense_entries(): void
|
||||
{
|
||||
$cashier = $this->createCashier();
|
||||
$order = $this->createPaymentOrder([
|
||||
'status' => PaymentOrder::STATUS_COMPLETED,
|
||||
]);
|
||||
|
||||
$entry = $this->createCashierLedgerEntry([
|
||||
'payment_order_id' => $order->id,
|
||||
'entry_type' => 'expense',
|
||||
]);
|
||||
|
||||
$this->assertEquals('expense', $entry->entry_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ledger balance calculation
|
||||
*/
|
||||
public function test_ledger_balance_calculation(): void
|
||||
{
|
||||
$cashier = $this->createCashier();
|
||||
|
||||
// Create income
|
||||
$this->createCashierLedgerEntry([
|
||||
'entry_type' => 'income',
|
||||
'amount' => 100000,
|
||||
]);
|
||||
|
||||
// Create expense
|
||||
$this->createCashierLedgerEntry([
|
||||
'entry_type' => 'expense',
|
||||
'amount' => 30000,
|
||||
]);
|
||||
|
||||
$balance = CashierLedger::calculateBalance();
|
||||
$this->assertEquals(70000, $balance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ledger date range filter
|
||||
*/
|
||||
public function test_ledger_date_range_filter(): void
|
||||
{
|
||||
$cashier = $this->createCashier();
|
||||
|
||||
$this->createCashierLedgerEntry([
|
||||
'entry_date' => now()->subMonth(),
|
||||
]);
|
||||
|
||||
$this->createCashierLedgerEntry([
|
||||
'entry_date' => now(),
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($cashier)->get(
|
||||
route('admin.cashier-ledger.index', [
|
||||
'start_date' => now()->startOfMonth()->toDateString(),
|
||||
'end_date' => now()->endOfMonth()->toDateString(),
|
||||
])
|
||||
);
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ledger entry validation
|
||||
*/
|
||||
public function test_ledger_entry_validation(): void
|
||||
{
|
||||
$cashier = $this->createCashier();
|
||||
|
||||
$response = $this->actingAs($cashier)->post(
|
||||
route('admin.cashier-ledger.store'),
|
||||
[
|
||||
'entry_type' => 'income',
|
||||
'amount' => -1000, // Invalid negative amount
|
||||
'entry_date' => now()->toDateString(),
|
||||
]
|
||||
);
|
||||
|
||||
$response->assertSessionHasErrors('amount');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ledger entry requires date
|
||||
*/
|
||||
public function test_ledger_entry_requires_date(): void
|
||||
{
|
||||
$cashier = $this->createCashier();
|
||||
|
||||
$response = $this->actingAs($cashier)->post(
|
||||
route('admin.cashier-ledger.store'),
|
||||
[
|
||||
'entry_type' => 'income',
|
||||
'amount' => 5000,
|
||||
// Missing entry_date
|
||||
]
|
||||
);
|
||||
|
||||
$response->assertSessionHasErrors('entry_date');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ledger monthly summary
|
||||
*/
|
||||
public function test_ledger_monthly_summary(): void
|
||||
{
|
||||
$cashier = $this->createCashier();
|
||||
|
||||
$this->createCashierLedgerEntry([
|
||||
'entry_type' => 'income',
|
||||
'amount' => 100000,
|
||||
'entry_date' => now(),
|
||||
]);
|
||||
|
||||
$this->createCashierLedgerEntry([
|
||||
'entry_type' => 'expense',
|
||||
'amount' => 50000,
|
||||
'entry_date' => now(),
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($cashier)->get(
|
||||
route('admin.cashier-ledger.summary', [
|
||||
'year' => now()->year,
|
||||
'month' => now()->month,
|
||||
])
|
||||
);
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ledger export
|
||||
*/
|
||||
public function test_ledger_export(): void
|
||||
{
|
||||
$cashier = $this->createCashier();
|
||||
|
||||
$this->createCashierLedgerEntry();
|
||||
$this->createCashierLedgerEntry();
|
||||
|
||||
$response = $this->actingAs($cashier)->get(
|
||||
route('admin.cashier-ledger.export', ['format' => 'csv'])
|
||||
);
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ledger entry cannot be edited after reconciliation
|
||||
*/
|
||||
public function test_ledger_entry_cannot_be_edited_after_reconciliation(): void
|
||||
{
|
||||
$cashier = $this->createCashier();
|
||||
$entry = $this->createCashierLedgerEntry([
|
||||
'is_reconciled' => true,
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($cashier)->patch(
|
||||
route('admin.cashier-ledger.update', $entry),
|
||||
['amount' => 99999]
|
||||
);
|
||||
|
||||
$response->assertSessionHasErrors();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user