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>
315 lines
10 KiB
PHP
315 lines
10 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\CashierLedgerEntry;
|
|
use App\Models\FinanceDocument;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Foundation\Testing\WithFaker;
|
|
use Spatie\Permission\Models\Role;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* Cashier Ledger Workflow Feature Tests
|
|
*
|
|
* Tests cashier ledger entry creation and balance tracking
|
|
*/
|
|
class CashierLedgerWorkflowTest extends TestCase
|
|
{
|
|
use RefreshDatabase, WithFaker;
|
|
|
|
protected User $cashier;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->withoutMiddleware([\App\Http\Middleware\EnsureUserIsAdmin::class, \App\Http\Middleware\VerifyCsrfToken::class]);
|
|
$this->artisan('db:seed', ['--class' => 'FinancialWorkflowPermissionsSeeder']);
|
|
|
|
\Spatie\Permission\Models\Permission::findOrCreate('record_cashier_ledger', 'web');
|
|
\Spatie\Permission\Models\Permission::findOrCreate('view_cashier_ledger', 'web');
|
|
|
|
Role::firstOrCreate(['name' => 'finance_cashier']);
|
|
|
|
$this->cashier = User::factory()->create(['email' => 'cashier@test.com']);
|
|
$this->cashier->assignRole('finance_cashier');
|
|
$this->cashier->givePermissionTo(['record_cashier_ledger', 'view_cashier_ledger']);
|
|
}
|
|
|
|
/** @test */
|
|
public function cashier_can_create_receipt_entry()
|
|
{
|
|
$this->actingAs($this->cashier);
|
|
|
|
$response = $this->post(route('admin.cashier-ledger.store'), [
|
|
'entry_type' => 'receipt',
|
|
'entry_date' => now()->format('Y-m-d'),
|
|
'amount' => 5000,
|
|
'payment_method' => 'bank_transfer',
|
|
'bank_account' => 'Test Bank 1234567890',
|
|
'receipt_number' => 'RCP001',
|
|
'notes' => 'Test receipt entry',
|
|
]);
|
|
|
|
$response->assertRedirect();
|
|
|
|
$this->assertDatabaseHas('cashier_ledger_entries', [
|
|
'entry_type' => 'receipt',
|
|
'amount' => 5000,
|
|
'bank_account' => 'Test Bank 1234567890',
|
|
'recorded_by_cashier_id' => $this->cashier->id,
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function receipt_entry_increases_balance()
|
|
{
|
|
// Create initial entry
|
|
$entry1 = CashierLedgerEntry::create([
|
|
'entry_type' => 'receipt',
|
|
'entry_date' => now(),
|
|
'amount' => 10000,
|
|
'payment_method' => 'cash',
|
|
'bank_account' => 'Test Account',
|
|
'balance_before' => 0,
|
|
'balance_after' => 10000,
|
|
'recorded_by_cashier_id' => $this->cashier->id,
|
|
'recorded_at' => now(),
|
|
]);
|
|
|
|
$this->assertEquals(10000, $entry1->balance_after);
|
|
|
|
// Create second receipt entry
|
|
$entry2 = CashierLedgerEntry::create([
|
|
'entry_type' => 'receipt',
|
|
'entry_date' => now(),
|
|
'amount' => 5000,
|
|
'payment_method' => 'cash',
|
|
'bank_account' => 'Test Account',
|
|
'balance_before' => 10000,
|
|
'balance_after' => 15000,
|
|
'recorded_by_cashier_id' => $this->cashier->id,
|
|
'recorded_at' => now(),
|
|
]);
|
|
|
|
$this->assertEquals(15000, $entry2->balance_after);
|
|
$this->assertEquals(15000, CashierLedgerEntry::getLatestBalance('Test Account'));
|
|
}
|
|
|
|
/** @test */
|
|
public function payment_entry_decreases_balance()
|
|
{
|
|
// Create initial balance
|
|
CashierLedgerEntry::create([
|
|
'entry_type' => 'receipt',
|
|
'entry_date' => now(),
|
|
'amount' => 20000,
|
|
'payment_method' => 'cash',
|
|
'bank_account' => 'Test Account',
|
|
'balance_before' => 0,
|
|
'balance_after' => 20000,
|
|
'recorded_by_cashier_id' => $this->cashier->id,
|
|
'recorded_at' => now(),
|
|
]);
|
|
|
|
// Create payment entry
|
|
$paymentEntry = CashierLedgerEntry::create([
|
|
'entry_type' => 'payment',
|
|
'entry_date' => now(),
|
|
'amount' => 8000,
|
|
'payment_method' => 'cash',
|
|
'bank_account' => 'Test Account',
|
|
'balance_before' => 20000,
|
|
'balance_after' => 12000,
|
|
'recorded_by_cashier_id' => $this->cashier->id,
|
|
'recorded_at' => now(),
|
|
]);
|
|
|
|
$this->assertEquals(12000, $paymentEntry->balance_after);
|
|
$this->assertEquals(12000, CashierLedgerEntry::getLatestBalance('Test Account'));
|
|
}
|
|
|
|
/** @test */
|
|
public function balance_calculation_is_correct()
|
|
{
|
|
$entry = new CashierLedgerEntry([
|
|
'entry_type' => 'receipt',
|
|
'amount' => 5000,
|
|
]);
|
|
|
|
$newBalance = $entry->calculateBalanceAfter(10000);
|
|
$this->assertEquals(15000, $newBalance);
|
|
|
|
$entry->entry_type = 'payment';
|
|
$newBalance = $entry->calculateBalanceAfter(10000);
|
|
$this->assertEquals(5000, $newBalance);
|
|
}
|
|
|
|
/** @test */
|
|
public function separate_balances_are_maintained_for_different_accounts()
|
|
{
|
|
// Account 1
|
|
CashierLedgerEntry::create([
|
|
'entry_type' => 'receipt',
|
|
'entry_date' => now(),
|
|
'amount' => 10000,
|
|
'payment_method' => 'bank_transfer',
|
|
'bank_account' => 'Bank A - 1111',
|
|
'balance_before' => 0,
|
|
'balance_after' => 10000,
|
|
'recorded_by_cashier_id' => $this->cashier->id,
|
|
'recorded_at' => now(),
|
|
]);
|
|
|
|
// Account 2
|
|
CashierLedgerEntry::create([
|
|
'entry_type' => 'receipt',
|
|
'entry_date' => now(),
|
|
'amount' => 5000,
|
|
'payment_method' => 'bank_transfer',
|
|
'bank_account' => 'Bank B - 2222',
|
|
'balance_before' => 0,
|
|
'balance_after' => 5000,
|
|
'recorded_by_cashier_id' => $this->cashier->id,
|
|
'recorded_at' => now(),
|
|
]);
|
|
|
|
$this->assertEquals(10000, CashierLedgerEntry::getLatestBalance('Bank A - 1111'));
|
|
$this->assertEquals(5000, CashierLedgerEntry::getLatestBalance('Bank B - 2222'));
|
|
}
|
|
|
|
/** @test */
|
|
public function ledger_entry_can_be_linked_to_finance_document()
|
|
{
|
|
$financeDoc = FinanceDocument::factory()->create([
|
|
'amount' => 3000,
|
|
'status' => 'approved_accountant',
|
|
]);
|
|
|
|
$entry = CashierLedgerEntry::create([
|
|
'finance_document_id' => $financeDoc->id,
|
|
'entry_type' => 'payment',
|
|
'entry_date' => now(),
|
|
'amount' => 3000,
|
|
'payment_method' => 'cash',
|
|
'bank_account' => 'Test Account',
|
|
'balance_before' => 10000,
|
|
'balance_after' => 7000,
|
|
'recorded_by_cashier_id' => $this->cashier->id,
|
|
'recorded_at' => now(),
|
|
]);
|
|
|
|
$this->assertEquals($financeDoc->id, $entry->finance_document_id);
|
|
$this->assertInstanceOf(FinanceDocument::class, $entry->financeDocument);
|
|
}
|
|
|
|
/** @test */
|
|
public function entry_type_helper_methods_work_correctly()
|
|
{
|
|
$receiptEntry = new CashierLedgerEntry(['entry_type' => 'receipt']);
|
|
$this->assertTrue($receiptEntry->isReceipt());
|
|
$this->assertFalse($receiptEntry->isPayment());
|
|
|
|
$paymentEntry = new CashierLedgerEntry(['entry_type' => 'payment']);
|
|
$this->assertTrue($paymentEntry->isPayment());
|
|
$this->assertFalse($paymentEntry->isReceipt());
|
|
}
|
|
|
|
/** @test */
|
|
public function payment_method_text_is_correctly_displayed()
|
|
{
|
|
$entry1 = new CashierLedgerEntry(['payment_method' => 'cash']);
|
|
$this->assertEquals('現金', $entry1->getPaymentMethodText());
|
|
|
|
$entry2 = new CashierLedgerEntry(['payment_method' => 'bank_transfer']);
|
|
$this->assertEquals('銀行轉帳', $entry2->getPaymentMethodText());
|
|
|
|
$entry3 = new CashierLedgerEntry(['payment_method' => 'check']);
|
|
$this->assertEquals('支票', $entry3->getPaymentMethodText());
|
|
}
|
|
|
|
/** @test */
|
|
public function cashier_can_view_balance_report()
|
|
{
|
|
// Create multiple entries
|
|
CashierLedgerEntry::create([
|
|
'entry_type' => 'receipt',
|
|
'entry_date' => now(),
|
|
'amount' => 100000,
|
|
'payment_method' => 'bank_transfer',
|
|
'bank_account' => 'Main Account',
|
|
'balance_before' => 0,
|
|
'balance_after' => 100000,
|
|
'recorded_by_cashier_id' => $this->cashier->id,
|
|
'recorded_at' => now(),
|
|
]);
|
|
|
|
CashierLedgerEntry::create([
|
|
'entry_type' => 'payment',
|
|
'entry_date' => now(),
|
|
'amount' => 30000,
|
|
'payment_method' => 'bank_transfer',
|
|
'bank_account' => 'Main Account',
|
|
'balance_before' => 100000,
|
|
'balance_after' => 70000,
|
|
'recorded_by_cashier_id' => $this->cashier->id,
|
|
'recorded_at' => now(),
|
|
]);
|
|
|
|
$this->actingAs($this->cashier);
|
|
|
|
$response = $this->get(route('admin.cashier-ledger.balance-report'));
|
|
|
|
$response->assertStatus(200);
|
|
$response->assertViewHas('accounts');
|
|
$response->assertViewHas('monthlySummary');
|
|
}
|
|
|
|
/** @test */
|
|
public function zero_balance_is_returned_for_new_account()
|
|
{
|
|
$balance = CashierLedgerEntry::getLatestBalance('New Account');
|
|
$this->assertEquals(0, $balance);
|
|
}
|
|
|
|
/** @test */
|
|
public function ledger_entries_can_be_filtered_by_date_range()
|
|
{
|
|
$this->actingAs($this->cashier);
|
|
|
|
// Create entries with different dates
|
|
CashierLedgerEntry::create([
|
|
'entry_type' => 'receipt',
|
|
'entry_date' => now()->subDays(10),
|
|
'amount' => 1000,
|
|
'payment_method' => 'cash',
|
|
'bank_account' => 'Test',
|
|
'balance_before' => 0,
|
|
'balance_after' => 1000,
|
|
'recorded_by_cashier_id' => $this->cashier->id,
|
|
'recorded_at' => now()->subDays(10),
|
|
]);
|
|
|
|
CashierLedgerEntry::create([
|
|
'entry_type' => 'receipt',
|
|
'entry_date' => now(),
|
|
'amount' => 2000,
|
|
'payment_method' => 'cash',
|
|
'bank_account' => 'Test',
|
|
'balance_before' => 1000,
|
|
'balance_after' => 3000,
|
|
'recorded_by_cashier_id' => $this->cashier->id,
|
|
'recorded_at' => now(),
|
|
]);
|
|
|
|
$response = $this->get(route('admin.cashier-ledger.index', [
|
|
'date_from' => now()->subDays(5)->format('Y-m-d'),
|
|
'date_to' => now()->format('Y-m-d'),
|
|
]));
|
|
|
|
$response->assertStatus(200);
|
|
}
|
|
}
|