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>
244 lines
6.8 KiB
PHP
244 lines
6.8 KiB
PHP
<?php
|
|
|
|
namespace Tests\Browser;
|
|
|
|
use App\Models\FinanceDocument;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Laravel\Dusk\Browser;
|
|
use Tests\DuskTestCase;
|
|
|
|
/**
|
|
* Finance Workflow Browser Tests
|
|
*
|
|
* Tests the finance workflow user interface and user experience.
|
|
*/
|
|
class FinanceWorkflowBrowserTest extends DuskTestCase
|
|
{
|
|
use DatabaseMigrations;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->artisan('db:seed', ['--class' => 'FinancialWorkflowPermissionsSeeder']);
|
|
}
|
|
|
|
/**
|
|
* Create a cashier user
|
|
*/
|
|
protected function createCashier(): User
|
|
{
|
|
$user = User::factory()->create([
|
|
'email' => 'cashier@test.com',
|
|
'password' => Hash::make('password'),
|
|
]);
|
|
$user->assignRole('finance_cashier');
|
|
|
|
return $user;
|
|
}
|
|
|
|
/**
|
|
* Create an accountant user
|
|
*/
|
|
protected function createAccountant(): User
|
|
{
|
|
$user = User::factory()->create([
|
|
'email' => 'accountant@test.com',
|
|
'password' => Hash::make('password'),
|
|
]);
|
|
$user->assignRole('finance_accountant');
|
|
|
|
return $user;
|
|
}
|
|
|
|
/**
|
|
* Test finance dashboard loads
|
|
*/
|
|
public function test_finance_dashboard_loads(): void
|
|
{
|
|
$cashier = $this->createCashier();
|
|
|
|
$this->browse(function (Browser $browser) use ($cashier) {
|
|
$browser->loginAs($cashier)
|
|
->visit(route('admin.finance.index'))
|
|
->assertSee('財務管理');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test can create finance document
|
|
*/
|
|
public function test_can_create_finance_document(): void
|
|
{
|
|
$accountant = $this->createAccountant();
|
|
|
|
$this->browse(function (Browser $browser) use ($accountant) {
|
|
$browser->loginAs($accountant)
|
|
->visit(route('admin.finance.create'))
|
|
->assertSee('新增財務單據')
|
|
->assertPresent('input[name="title"]')
|
|
->assertPresent('input[name="amount"]');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test finance document list shows status
|
|
*/
|
|
public function test_finance_document_list_shows_status(): void
|
|
{
|
|
$cashier = $this->createCashier();
|
|
|
|
FinanceDocument::factory()->create([
|
|
'status' => FinanceDocument::STATUS_PENDING,
|
|
'title' => '測試單據',
|
|
]);
|
|
|
|
$this->browse(function (Browser $browser) use ($cashier) {
|
|
$browser->loginAs($cashier)
|
|
->visit(route('admin.finance.index'))
|
|
->assertSee('測試單據')
|
|
->assertSee('待審核');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test cashier can see approve button
|
|
*/
|
|
public function test_cashier_can_see_approve_button(): void
|
|
{
|
|
$cashier = $this->createCashier();
|
|
|
|
$document = FinanceDocument::factory()->create([
|
|
'status' => FinanceDocument::STATUS_PENDING,
|
|
]);
|
|
|
|
$this->browse(function (Browser $browser) use ($cashier, $document) {
|
|
$browser->loginAs($cashier)
|
|
->visit(route('admin.finance.show', $document))
|
|
->assertSee('核准');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test cashier can see reject button
|
|
*/
|
|
public function test_cashier_can_see_reject_button(): void
|
|
{
|
|
$cashier = $this->createCashier();
|
|
|
|
$document = FinanceDocument::factory()->create([
|
|
'status' => FinanceDocument::STATUS_PENDING,
|
|
]);
|
|
|
|
$this->browse(function (Browser $browser) use ($cashier, $document) {
|
|
$browser->loginAs($cashier)
|
|
->visit(route('admin.finance.show', $document))
|
|
->assertSee('退回');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test approval requires confirmation
|
|
*/
|
|
public function test_approval_requires_confirmation(): void
|
|
{
|
|
$cashier = $this->createCashier();
|
|
|
|
$document = FinanceDocument::factory()->create([
|
|
'status' => FinanceDocument::STATUS_PENDING,
|
|
]);
|
|
|
|
$this->browse(function (Browser $browser) use ($cashier, $document) {
|
|
$browser->loginAs($cashier)
|
|
->visit(route('admin.finance.show', $document))
|
|
->press('核准')
|
|
->assertDialogOpened();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test document amount is formatted
|
|
*/
|
|
public function test_document_amount_is_formatted(): void
|
|
{
|
|
$cashier = $this->createCashier();
|
|
|
|
FinanceDocument::factory()->create([
|
|
'status' => FinanceDocument::STATUS_PENDING,
|
|
'amount' => 15000,
|
|
]);
|
|
|
|
$this->browse(function (Browser $browser) use ($cashier) {
|
|
$browser->loginAs($cashier)
|
|
->visit(route('admin.finance.index'))
|
|
->assertSee('15,000');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test filter by status works
|
|
*/
|
|
public function test_filter_by_status_works(): void
|
|
{
|
|
$cashier = $this->createCashier();
|
|
|
|
FinanceDocument::factory()->create([
|
|
'status' => FinanceDocument::STATUS_PENDING,
|
|
'title' => '待審核單據',
|
|
]);
|
|
|
|
FinanceDocument::factory()->create([
|
|
'status' => FinanceDocument::STATUS_APPROVED_CASHIER,
|
|
'title' => '已核准單據',
|
|
]);
|
|
|
|
$this->browse(function (Browser $browser) use ($cashier) {
|
|
$browser->loginAs($cashier)
|
|
->visit(route('admin.finance.index'))
|
|
->select('status', FinanceDocument::STATUS_PENDING)
|
|
->press('篩選')
|
|
->assertSee('待審核單據')
|
|
->assertDontSee('已核准單據');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test rejection requires reason
|
|
*/
|
|
public function test_rejection_requires_reason(): void
|
|
{
|
|
$cashier = $this->createCashier();
|
|
|
|
$document = FinanceDocument::factory()->create([
|
|
'status' => FinanceDocument::STATUS_PENDING,
|
|
]);
|
|
|
|
$this->browse(function (Browser $browser) use ($cashier, $document) {
|
|
$browser->loginAs($cashier)
|
|
->visit(route('admin.finance.show', $document))
|
|
->press('退回')
|
|
->waitFor('.modal')
|
|
->assertSee('退回原因');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test document history is visible
|
|
*/
|
|
public function test_document_history_is_visible(): void
|
|
{
|
|
$cashier = $this->createCashier();
|
|
|
|
$document = FinanceDocument::factory()->create([
|
|
'status' => FinanceDocument::STATUS_APPROVED_CASHIER,
|
|
]);
|
|
|
|
$this->browse(function (Browser $browser) use ($cashier, $document) {
|
|
$browser->loginAs($cashier)
|
|
->visit(route('admin.finance.show', $document))
|
|
->assertSee('審核歷程');
|
|
});
|
|
}
|
|
}
|