Initial commit

This commit is contained in:
2025-11-20 23:21:05 +08:00
commit 13bc6db529
378 changed files with 54527 additions and 0 deletions

View File

@@ -0,0 +1,540 @@
# Financial Workflow System - Test Plan
## Overview
This document outlines the complete testing strategy for the financial workflow system implementing the "會計管帳,出納管錢" (Accountant manages books, Cashier manages money) principle.
---
## Test Environment Setup
### Prerequisites
```bash
# 1. Run setup script
./setup-financial-workflow.sh
# 2. Verify test users created
php artisan tinker
>>> User::where('email', 'like', '%@test.com')->pluck('email', 'name')
# 3. Check permissions
>>> Role::with('permissions')->get()
```
### Test Users
| Email | Password | Role | Purpose |
|-------|----------|------|---------|
| cashier@test.com | password | finance_cashier | Test cashier operations |
| accountant@test.com | password | finance_accountant | Test accountant operations |
| chair@test.com | password | finance_chair | Test chair approvals |
| requester@test.com | password | finance_requester | Test document creation |
---
## 1. Manual Testing Checklist
### 1.1 Stage 1: Approval Workflow
#### Small Amount (< 5,000) - Cashier → Accountant
- [ ] **Step 1**: Login as `requester@test.com`
- [ ] Navigate to `/admin/finance-documents/create`
- [ ] Create document with:
- Title: "小額報銷測試"
- Amount: 3,000
- Request Type: expense_reimbursement
- Upload attachment
- [ ] Verify document created with status "pending"
- [ ] Verify amount_tier automatically set to "small"
- [ ] **Step 2**: Login as `cashier@test.com`
- [ ] Navigate to `/admin/finance-documents`
- [ ] Find pending document
- [ ] Click "Approve"
- [ ] Verify status changed to "approved_cashier"
- [ ] Verify email sent to accountant
- [ ] **Step 3**: Login as `accountant@test.com`
- [ ] View document
- [ ] Click "Approve"
- [ ] Verify status changed to "approved_accountant"
- [ ] Verify message shows "小額申請審核完成,可以製作付款單"
- [ ] Verify "Create Payment Order" button appears
#### Medium Amount (5,000-50,000) - Cashier → Accountant → Chair
- [ ] **Step 1**: Create document with amount: 25,000
- [ ] **Step 2**: Cashier approves
- [ ] **Step 3**: Accountant approves
- [ ] Verify message shows "已送交理事長審核"
- [ ] **Step 4**: Login as `chair@test.com`
- [ ] Approve document
- [ ] Verify status changed to "approved_chair"
- [ ] Verify message shows "審核流程完成"
#### Large Amount (> 50,000) - Cashier → Accountant → Chair → Board
- [ ] **Step 1**: Create document with amount: 75,000
- [ ] **Step 2-4**: Complete cashier, accountant, chair approvals
- [ ] **Step 5**: Verify `requires_board_meeting` flag is true
- [ ] **Step 6**: Verify message shows "大額申請仍需理事會核准"
---
### 1.2 Stage 2: Payment Workflow
#### Create Payment Order (Accountant)
- [ ] **Step 1**: Login as `accountant@test.com`
- [ ] **Step 2**: Navigate to approved document
- [ ] **Step 3**: Click "製作付款單"
- [ ] **Step 4**: Fill payment order form:
- Payee Name: "Test Vendor"
- Payment Method: "bank_transfer"
- Bank Name: "Test Bank"
- Bank Code: "007"
- Account Number: "1234567890"
- Amount: (auto-filled from document)
- Notes: "測試付款單"
- [ ] **Step 5**: Submit form
- [ ] **Step 6**: Verify:
- [ ] Payment order created with unique number (PO-YYYYMMDD-####)
- [ ] Status is "pending_verification"
- [ ] finance_document updated with payment_order_created_at
- [ ] Redirect to payment order show page
#### Verify Payment Order (Cashier)
- [ ] **Step 1**: Login as `cashier@test.com`
- [ ] **Step 2**: Navigate to `/admin/payment-orders`
- [ ] **Step 3**: Find pending payment order
- [ ] **Step 4**: Click to view details
- [ ] **Step 5**: Review payment information
- [ ] **Step 6**: Option A - Approve:
- [ ] Enter verification notes
- [ ] Click "通過覆核"
- [ ] Verify status changed to "verified"
- [ ] Verify execution form appears
- [ ] **Step 7**: Option B - Reject:
- [ ] Enter rejection reason
- [ ] Click "駁回"
- [ ] Verify status changed to "cancelled"
#### Execute Payment (Cashier)
- [ ] **Step 1**: With verified payment order
- [ ] **Step 2**: Fill execution form:
- Transaction Reference: "TXN-2025-001"
- Upload payment receipt (PDF/image)
- Execution notes: "已完成轉帳"
- [ ] **Step 3**: Click "確認執行付款"
- [ ] **Step 4**: Verify:
- [ ] Status changed to "executed"
- [ ] Execution status is "completed"
- [ ] Receipt can be downloaded
- [ ] finance_document updated with payment_executed_at
---
### 1.3 Stage 3: Recording Workflow
#### Cashier Ledger Entry
- [ ] **Step 1**: Login as `cashier@test.com`
- [ ] **Step 2**: Navigate to `/admin/cashier-ledger/create`
- [ ] **Step 3**: Fill form:
- Finance Document: (select executed payment)
- Entry Date: (today)
- Entry Type: "payment"
- Payment Method: "bank_transfer"
- Bank Account: "Main Account"
- Amount: (from payment order)
- Receipt Number: "RCP-001"
- Transaction Reference: (from payment order)
- Notes: "記錄付款"
- [ ] **Step 4**: Submit form
- [ ] **Step 5**: Verify:
- [ ] Entry created
- [ ] Balance_before calculated from previous entry
- [ ] Balance_after = balance_before - amount
- [ ] finance_document updated with cashier_ledger_entry_id
#### Accounting Transaction (Accountant)
- [ ] **Step 1**: Login as `accountant@test.com`
- [ ] **Step 2**: Navigate to `/admin/transactions/create`
- [ ] **Step 3**: Create accounting entry with debit/credit
- [ ] **Step 4**: Link to finance document
- [ ] **Step 5**: Verify transaction recorded
---
### 1.4 Stage 4: Reconciliation Workflow
#### Prepare Bank Reconciliation (Cashier)
- [ ] **Step 1**: Login as `cashier@test.com`
- [ ] **Step 2**: Navigate to `/admin/bank-reconciliations/create`
- [ ] **Step 3**: Fill reconciliation form:
- Reconciliation Month: "2025-11"
- Bank Statement Balance: 500,000
- Bank Statement Date: 2025-11-30
- Upload bank statement (PDF)
- System Book Balance: (auto-calculated from ledger)
- [ ] **Step 4**: Add outstanding items:
- Outstanding checks: [{"amount": 5000, "check_number": "CHK-001"}]
- Deposits in transit: [{"amount": 10000, "date": "2025-11-29"}]
- Bank charges: [{"amount": 50, "description": "Service fee"}]
- [ ] **Step 5**: Submit form
- [ ] **Step 6**: Verify:
- [ ] Reconciliation created
- [ ] Adjusted balance calculated correctly
- [ ] Discrepancy detected if amounts don't match
- [ ] Status based on discrepancy
#### Review Bank Reconciliation (Accountant)
- [ ] **Step 1**: Login as `accountant@test.com`
- [ ] **Step 2**: Navigate to pending reconciliation
- [ ] **Step 3**: Review outstanding items
- [ ] **Step 4**: Click "Review"
- [ ] **Step 5**: Verify reviewed_at timestamp set
#### Approve Bank Reconciliation (Chair)
- [ ] **Step 1**: Login as `chair@test.com`
- [ ] **Step 2**: Navigate to reviewed reconciliation
- [ ] **Step 3**: Click "Approve"
- [ ] **Step 4**: Verify:
- [ ] Status changed to "completed" or "discrepancy"
- [ ] Approved_at timestamp set
---
## 2. Automated Tests
### 2.1 Feature Tests
Create file: `tests/Feature/FinancialWorkflowTest.php`
```php
<?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\User;
use App\Models\FinanceDocument;
use App\Models\PaymentOrder;
use App\Models\CashierLedgerEntry;
use App\Models\BankReconciliation;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Spatie\Permission\Models\Role;
class FinancialWorkflowTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
$this->seed(FinancialWorkflowPermissionsSeeder::class);
}
/** @test */
public function small_amount_workflow_completes_without_chair()
{
// Create users
$cashier = User::factory()->create();
$accountant = User::factory()->create();
$requester = User::factory()->create();
$cashier->assignRole('finance_cashier');
$accountant->assignRole('finance_accountant');
$requester->assignRole('finance_requester');
// Step 1: Requester submits
$document = FinanceDocument::create([
'submitted_by_user_id' => $requester->id,
'title' => 'Small Expense',
'amount' => 3000,
'request_type' => 'expense_reimbursement',
'status' => 'pending',
'submitted_at' => now(),
]);
$document->amount_tier = $document->determineAmountTier();
$document->save();
$this->assertEquals('small', $document->amount_tier);
// Step 2: Cashier approves
$this->actingAs($cashier)
->post(route('admin.finance.approve', $document))
->assertRedirect();
$document->refresh();
$this->assertEquals('approved_cashier', $document->status);
// Step 3: Accountant approves
$this->actingAs($accountant)
->post(route('admin.finance.approve', $document))
->assertRedirect();
$document->refresh();
$this->assertEquals('approved_accountant', $document->status);
$this->assertTrue($document->isApprovalStageComplete());
}
/** @test */
public function medium_amount_requires_chair_approval()
{
// Similar test for medium amount...
}
/** @test */
public function accountant_can_create_payment_order()
{
// Test payment order creation...
}
/** @test */
public function cashier_can_verify_payment_order()
{
// Test payment verification...
}
/** @test */
public function cashier_can_execute_payment()
{
// Test payment execution...
}
/** @test */
public function cashier_ledger_calculates_balance_correctly()
{
// Test balance calculation...
}
/** @test */
public function bank_reconciliation_detects_discrepancy()
{
// Test discrepancy detection...
}
}
```
### 2.2 Unit Tests
Create file: `tests/Unit/PaymentOrderTest.php`
```php
<?php
namespace Tests\Unit;
use Tests\TestCase;
use App\Models\PaymentOrder;
use Illuminate\Foundation\Testing\RefreshDatabase;
class PaymentOrderTest extends TestCase
{
use RefreshDatabase;
/** @test */
public function generates_unique_payment_order_number()
{
$number1 = PaymentOrder::generatePaymentOrderNumber();
$number2 = PaymentOrder::generatePaymentOrderNumber();
$this->assertStringStartsWith('PO-', $number1);
$this->assertStringStartsWith('PO-', $number2);
$this->assertNotEquals($number1, $number2);
}
/** @test */
public function can_be_verified_when_pending()
{
$order = PaymentOrder::factory()->create([
'status' => 'pending_verification',
'verification_status' => 'pending',
]);
$this->assertTrue($order->canBeVerifiedByCashier());
}
/** @test */
public function cannot_be_verified_when_already_verified()
{
$order = PaymentOrder::factory()->create([
'status' => 'verified',
'verification_status' => 'approved',
]);
$this->assertFalse($order->canBeVerifiedByCashier());
}
/** @test */
public function can_be_executed_when_verified_and_approved()
{
$order = PaymentOrder::factory()->create([
'status' => 'verified',
'verification_status' => 'approved',
'execution_status' => 'pending',
]);
$this->assertTrue($order->canBeExecuted());
}
}
```
### 2.3 Integration Tests
```php
/** @test */
public function complete_workflow_from_document_to_reconciliation()
{
// 1. Create and approve document
// 2. Create payment order
// 3. Verify payment order
// 4. Execute payment
// 5. Record in cashier ledger
// 6. Create accounting transaction
// 7. Perform bank reconciliation
// Assert all steps completed successfully
}
```
---
## 3. Permission Tests
### Test Permission Enforcement
```php
/** @test */
public function non_cashier_cannot_verify_payment()
{
$accountant = User::factory()->create();
$accountant->assignRole('finance_accountant');
$order = PaymentOrder::factory()->create([
'status' => 'pending_verification',
]);
$this->actingAs($accountant)
->post(route('admin.payment-orders.verify', $order))
->assertForbidden();
}
/** @test */
public function non_accountant_cannot_create_payment_order()
{
$cashier = User::factory()->create();
$cashier->assignRole('finance_cashier');
$document = FinanceDocument::factory()->create([
'status' => 'approved_accountant',
]);
$this->actingAs($cashier)
->post(route('admin.payment-orders.store', $document), [
'payee_name' => 'Test',
'payment_amount' => 1000,
'payment_method' => 'cash',
])
->assertForbidden();
}
```
---
## 4. Edge Cases and Error Handling
### Test Cases
- [ ] Cannot approve already approved document
- [ ] Cannot verify already verified payment order
- [ ] Cannot execute payment without verification
- [ ] Cannot create payment order for unapproved document
- [ ] Balance calculation with negative balance
- [ ] Bank reconciliation with exact match (no discrepancy)
- [ ] Bank reconciliation with large discrepancy
- [ ] File upload size limits
- [ ] Invalid file types
- [ ] Missing required fields
- [ ] Concurrent access to same document
- [ ] Cancelling executed payment order (should fail)
---
## 5. Performance Tests
### Load Testing Checklist
- [ ] Create 1,000 finance documents
- [ ] Create 1,000 payment orders
- [ ] Create 10,000 ledger entries
- [ ] Test pagination performance
- [ ] Test search/filter performance
- [ ] Test balance calculation with large datasets
- [ ] Test bank reconciliation with many outstanding items
---
## 6. Security Tests
### Security Checklist
- [ ] SQL injection in search filters
- [ ] XSS in notes fields
- [ ] CSRF token validation
- [ ] File upload security (malicious files)
- [ ] Path traversal in file downloads
- [ ] Authorization bypass attempts
- [ ] Rate limiting on sensitive operations
---
## 7. User Acceptance Testing (UAT)
### UAT Checklist
- [ ] UI is intuitive and easy to navigate
- [ ] Error messages are clear and helpful
- [ ] Success messages provide adequate feedback
- [ ] Forms validate input properly
- [ ] Tables display data correctly
- [ ] Pagination works smoothly
- [ ] Filters work as expected
- [ ] File uploads work reliably
- [ ] Downloads work correctly
- [ ] Email notifications are received
- [ ] Mobile responsiveness (if applicable)
---
## 8. Regression Testing
### After Each Change
- [ ] Run all automated tests
- [ ] Test complete workflow manually
- [ ] Verify no existing functionality broken
- [ ] Check database integrity
- [ ] Verify audit logs still working
---
## Test Execution Log
| Date | Tester | Test Section | Status | Notes |
|------|--------|--------------|--------|-------|
| | | | | |
---
## Bugs/Issues Tracker
| ID | Priority | Description | Steps to Reproduce | Status | Fixed By |
|----|----------|-------------|-------------------|--------|----------|
| | | | | | |
---
## Sign-off
- [ ] All manual tests passed
- [ ] All automated tests passing
- [ ] Performance acceptable
- [ ] Security verified
- [ ] UAT completed
- [ ] Documentation complete
**Tested by**: ________________
**Date**: ________________
**Approved by**: ________________
**Date**: ________________