Features: - Support login via phone number or email (LoginRequest) - Add members:import-roster command for Excel roster import - Merge survey emails with roster data Code Quality (Phase 1-4): - Add database locking for balance calculation - Add self-approval checks for finance workflow - Create service layer (FinanceDocumentApprovalService, PaymentVerificationService) - Add HasAccountingEntries and HasApprovalWorkflow traits - Create FormRequest classes for validation - Add status-badge component - Define authorization gates in AuthServiceProvider - Add accounting config file Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
191 lines
5.2 KiB
PHP
191 lines
5.2 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\BankReconciliation;
|
|
|
|
use App\Models\BankReconciliation;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Http\UploadedFile;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Tests\TestCase;
|
|
use Tests\Traits\CreatesFinanceData;
|
|
use Tests\Traits\SeedsRolesAndPermissions;
|
|
|
|
/**
|
|
* Bank Reconciliation Tests
|
|
*
|
|
* Tests bank reconciliation in the finance workflow.
|
|
*/
|
|
class BankReconciliationTest extends TestCase
|
|
{
|
|
use RefreshDatabase, SeedsRolesAndPermissions, CreatesFinanceData;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
Storage::fake('local');
|
|
$this->seedRolesAndPermissions();
|
|
}
|
|
|
|
/**
|
|
* Test can view bank reconciliation page
|
|
*/
|
|
public function test_can_view_bank_reconciliation_page(): void
|
|
{
|
|
$accountant = $this->createAccountant();
|
|
|
|
$response = $this->actingAs($accountant)->get(
|
|
route('admin.bank-reconciliations.index')
|
|
);
|
|
|
|
$response->assertStatus(200);
|
|
}
|
|
|
|
/**
|
|
* Test can view create reconciliation form
|
|
*/
|
|
public function test_can_view_create_reconciliation_form(): void
|
|
{
|
|
$cashier = $this->createCashier();
|
|
|
|
$response = $this->actingAs($cashier)->get(
|
|
route('admin.bank-reconciliations.create')
|
|
);
|
|
|
|
$response->assertStatus(200);
|
|
}
|
|
|
|
/**
|
|
* Test reconciliation detects discrepancy
|
|
*/
|
|
public function test_reconciliation_detects_discrepancy(): void
|
|
{
|
|
$reconciliation = $this->createBankReconciliation([
|
|
'bank_statement_balance' => 500000,
|
|
'system_book_balance' => 480000,
|
|
'discrepancy_amount' => 20000,
|
|
]);
|
|
|
|
$this->assertEquals(20000, $reconciliation->discrepancy_amount);
|
|
}
|
|
|
|
/**
|
|
* Test reconciliation status tracking
|
|
*/
|
|
public function test_reconciliation_status_tracking(): void
|
|
{
|
|
$reconciliation = $this->createBankReconciliation([
|
|
'reconciliation_status' => 'pending',
|
|
]);
|
|
|
|
$this->assertEquals('pending', $reconciliation->reconciliation_status);
|
|
}
|
|
|
|
/**
|
|
* Test reconciliation approval
|
|
*/
|
|
public function test_reconciliation_approval(): void
|
|
{
|
|
$manager = $this->createChair();
|
|
$accountant = $this->createAccountant();
|
|
|
|
// Reconciliation must be reviewed before it can be approved
|
|
$reconciliation = $this->createBankReconciliation([
|
|
'reconciliation_status' => 'pending',
|
|
'reviewed_by_accountant_id' => $accountant->id,
|
|
'reviewed_at' => now(),
|
|
]);
|
|
|
|
$response = $this->actingAs($manager)->post(
|
|
route('admin.bank-reconciliations.approve', $reconciliation)
|
|
);
|
|
|
|
$reconciliation->refresh();
|
|
$this->assertEquals('completed', $reconciliation->reconciliation_status);
|
|
}
|
|
|
|
/**
|
|
* Test reconciliation date filter
|
|
*/
|
|
public function test_reconciliation_date_filter(): void
|
|
{
|
|
$accountant = $this->createAccountant();
|
|
|
|
$this->createBankReconciliation([
|
|
'reconciliation_month' => now()->subMonth()->startOfMonth(),
|
|
]);
|
|
|
|
$this->createBankReconciliation([
|
|
'reconciliation_month' => now()->startOfMonth(),
|
|
]);
|
|
|
|
$response = $this->actingAs($accountant)->get(
|
|
route('admin.bank-reconciliations.index')
|
|
);
|
|
|
|
$response->assertStatus(200);
|
|
}
|
|
|
|
/**
|
|
* Test reconciliation list shows history
|
|
*/
|
|
public function test_reconciliation_list_shows_history(): void
|
|
{
|
|
$accountant = $this->createAccountant();
|
|
|
|
for ($i = 0; $i < 3; $i++) {
|
|
$this->createBankReconciliation([
|
|
'reconciliation_month' => now()->subMonths($i)->startOfMonth(),
|
|
]);
|
|
}
|
|
|
|
$response = $this->actingAs($accountant)->get(
|
|
route('admin.bank-reconciliations.index')
|
|
);
|
|
|
|
$response->assertStatus(200);
|
|
}
|
|
|
|
/**
|
|
* Test only authorized users can reconcile
|
|
*/
|
|
public function test_only_authorized_users_can_reconcile(): void
|
|
{
|
|
$regularUser = User::factory()->create();
|
|
|
|
$response = $this->actingAs($regularUser)->get(
|
|
route('admin.bank-reconciliations.index')
|
|
);
|
|
|
|
$response->assertStatus(403);
|
|
}
|
|
|
|
/**
|
|
* Test can view reconciliation details
|
|
*/
|
|
public function test_can_view_reconciliation_details(): void
|
|
{
|
|
$accountant = $this->createAccountant();
|
|
$reconciliation = $this->createBankReconciliation();
|
|
|
|
$response = $this->actingAs($accountant)->get(
|
|
route('admin.bank-reconciliations.show', $reconciliation)
|
|
);
|
|
|
|
$response->assertStatus(200);
|
|
}
|
|
|
|
/**
|
|
* Test completed reconciliation creation
|
|
*/
|
|
public function test_completed_reconciliation_has_all_approvals(): void
|
|
{
|
|
$reconciliation = $this->createCompletedReconciliation();
|
|
|
|
$this->assertEquals('completed', $reconciliation->reconciliation_status);
|
|
$this->assertNotNull($reconciliation->prepared_by_cashier_id);
|
|
$this->assertNotNull($reconciliation->reviewed_by_accountant_id);
|
|
$this->assertNotNull($reconciliation->approved_by_manager_id);
|
|
}
|
|
}
|