Initial commit
This commit is contained in:
540
tests/FINANCIAL_WORKFLOW_TEST_PLAN.md
Normal file
540
tests/FINANCIAL_WORKFLOW_TEST_PLAN.md
Normal 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**: ________________
|
||||
Reference in New Issue
Block a user