313 lines
9.5 KiB
PHP
313 lines
9.5 KiB
PHP
<?php
|
||
|
||
namespace Tests\Unit;
|
||
|
||
use App\Models\FinanceDocument;
|
||
use App\Models\User;
|
||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||
use Tests\TestCase;
|
||
|
||
/**
|
||
* Finance Document Model Unit Tests
|
||
*
|
||
* Tests business logic methods in FinanceDocument model
|
||
*/
|
||
class FinanceDocumentTest extends TestCase
|
||
{
|
||
use RefreshDatabase;
|
||
|
||
/** @test */
|
||
public function it_determines_small_amount_tier_correctly()
|
||
{
|
||
$document = new FinanceDocument(['amount' => 4999]);
|
||
$this->assertEquals('small', $document->determineAmountTier());
|
||
|
||
$document->amount = 3000;
|
||
$this->assertEquals('small', $document->determineAmountTier());
|
||
|
||
$document->amount = 1;
|
||
$this->assertEquals('small', $document->determineAmountTier());
|
||
}
|
||
|
||
/** @test */
|
||
public function it_determines_medium_amount_tier_correctly()
|
||
{
|
||
$document = new FinanceDocument(['amount' => 5000]);
|
||
$this->assertEquals('medium', $document->determineAmountTier());
|
||
|
||
$document->amount = 25000;
|
||
$this->assertEquals('medium', $document->determineAmountTier());
|
||
|
||
$document->amount = 50000;
|
||
$this->assertEquals('medium', $document->determineAmountTier());
|
||
}
|
||
|
||
/** @test */
|
||
public function it_determines_large_amount_tier_correctly()
|
||
{
|
||
$document = new FinanceDocument(['amount' => 50001]);
|
||
$this->assertEquals('large', $document->determineAmountTier());
|
||
|
||
$document->amount = 100000;
|
||
$this->assertEquals('large', $document->determineAmountTier());
|
||
|
||
$document->amount = 1000000;
|
||
$this->assertEquals('large', $document->determineAmountTier());
|
||
}
|
||
|
||
/** @test */
|
||
public function small_amount_does_not_need_board_meeting()
|
||
{
|
||
$document = new FinanceDocument(['amount' => 4999]);
|
||
$this->assertFalse($document->needsBoardMeetingApproval());
|
||
}
|
||
|
||
/** @test */
|
||
public function medium_amount_does_not_need_board_meeting()
|
||
{
|
||
$document = new FinanceDocument(['amount' => 50000]);
|
||
$this->assertFalse($document->needsBoardMeetingApproval());
|
||
}
|
||
|
||
/** @test */
|
||
public function large_amount_needs_board_meeting()
|
||
{
|
||
$document = new FinanceDocument(['amount' => 50001]);
|
||
$this->assertTrue($document->needsBoardMeetingApproval());
|
||
}
|
||
|
||
/** @test */
|
||
public function small_amount_approval_stage_is_complete_after_accountant()
|
||
{
|
||
$document = new FinanceDocument([
|
||
'amount' => 3000,
|
||
'amount_tier' => 'small',
|
||
'status' => FinanceDocument::STATUS_APPROVED_ACCOUNTANT,
|
||
]);
|
||
|
||
$this->assertTrue($document->isApprovalStageComplete());
|
||
}
|
||
|
||
/** @test */
|
||
public function medium_amount_approval_stage_needs_chair()
|
||
{
|
||
$document = new FinanceDocument([
|
||
'amount' => 25000,
|
||
'amount_tier' => 'medium',
|
||
'status' => FinanceDocument::STATUS_APPROVED_ACCOUNTANT,
|
||
]);
|
||
|
||
$this->assertFalse($document->isApprovalStageComplete());
|
||
|
||
$document->status = FinanceDocument::STATUS_APPROVED_CHAIR;
|
||
$this->assertTrue($document->isApprovalStageComplete());
|
||
}
|
||
|
||
/** @test */
|
||
public function large_amount_approval_stage_needs_chair_and_board()
|
||
{
|
||
$document = new FinanceDocument([
|
||
'amount' => 75000,
|
||
'amount_tier' => 'large',
|
||
'status' => FinanceDocument::STATUS_APPROVED_CHAIR,
|
||
'board_meeting_approved_at' => null,
|
||
]);
|
||
|
||
$this->assertFalse($document->isApprovalStageComplete());
|
||
|
||
$document->board_meeting_approved_at = now();
|
||
$this->assertTrue($document->isApprovalStageComplete());
|
||
}
|
||
|
||
/** @test */
|
||
public function cashier_cannot_approve_own_submission()
|
||
{
|
||
$user = User::factory()->create();
|
||
|
||
$document = new FinanceDocument([
|
||
'submitted_by_id' => $user->id,
|
||
'status' => 'pending',
|
||
]);
|
||
|
||
$this->assertFalse($document->canBeApprovedByCashier($user));
|
||
}
|
||
|
||
/** @test */
|
||
public function cashier_can_approve_others_submission()
|
||
{
|
||
$submitter = User::factory()->create();
|
||
$cashier = User::factory()->create();
|
||
|
||
$document = new FinanceDocument([
|
||
'submitted_by_id' => $submitter->id,
|
||
'status' => 'pending',
|
||
]);
|
||
|
||
$this->assertTrue($document->canBeApprovedByCashier($cashier));
|
||
}
|
||
|
||
/** @test */
|
||
public function accountant_cannot_approve_before_cashier()
|
||
{
|
||
$document = new FinanceDocument([
|
||
'status' => 'pending',
|
||
]);
|
||
|
||
$this->assertFalse($document->canBeApprovedByAccountant());
|
||
}
|
||
|
||
/** @test */
|
||
public function accountant_can_approve_after_cashier()
|
||
{
|
||
$document = new FinanceDocument([
|
||
'status' => FinanceDocument::STATUS_APPROVED_CASHIER,
|
||
]);
|
||
|
||
$this->assertTrue($document->canBeApprovedByAccountant());
|
||
}
|
||
|
||
/** @test */
|
||
public function chair_cannot_approve_before_accountant()
|
||
{
|
||
$document = new FinanceDocument([
|
||
'status' => FinanceDocument::STATUS_APPROVED_CASHIER,
|
||
'amount_tier' => 'medium',
|
||
]);
|
||
|
||
$this->assertFalse($document->canBeApprovedByChair());
|
||
}
|
||
|
||
/** @test */
|
||
public function chair_can_approve_after_accountant_for_medium_amounts()
|
||
{
|
||
$document = new FinanceDocument([
|
||
'status' => FinanceDocument::STATUS_APPROVED_ACCOUNTANT,
|
||
'amount_tier' => 'medium',
|
||
]);
|
||
|
||
$this->assertTrue($document->canBeApprovedByChair());
|
||
}
|
||
|
||
/** @test */
|
||
public function payment_order_can_be_created_after_approval_stage()
|
||
{
|
||
$document = new FinanceDocument([
|
||
'amount' => 3000,
|
||
'amount_tier' => 'small',
|
||
'status' => FinanceDocument::STATUS_APPROVED_ACCOUNTANT,
|
||
]);
|
||
|
||
$this->assertTrue($document->canCreatePaymentOrder());
|
||
}
|
||
|
||
/** @test */
|
||
public function payment_order_cannot_be_created_before_approval_complete()
|
||
{
|
||
$document = new FinanceDocument([
|
||
'status' => FinanceDocument::STATUS_APPROVED_CASHIER,
|
||
]);
|
||
|
||
$this->assertFalse($document->canCreatePaymentOrder());
|
||
}
|
||
|
||
/** @test */
|
||
public function workflow_stages_are_correctly_identified()
|
||
{
|
||
$document = new FinanceDocument([
|
||
'status' => 'pending',
|
||
'amount_tier' => 'small',
|
||
]);
|
||
|
||
// Stage 1: Approval
|
||
$this->assertEquals('approval', $document->getCurrentWorkflowStage());
|
||
|
||
// Stage 2: Payment
|
||
$document->status = FinanceDocument::STATUS_APPROVED_ACCOUNTANT;
|
||
$document->cashier_approved_at = now();
|
||
$document->accountant_approved_at = now();
|
||
$document->payment_order_created_at = now();
|
||
$this->assertEquals('payment', $document->getCurrentWorkflowStage());
|
||
|
||
// Stage 3: Recording
|
||
$document->payment_executed_at = now();
|
||
$this->assertEquals('payment', $document->getCurrentWorkflowStage());
|
||
|
||
$document->cashier_recorded_at = now();
|
||
$this->assertEquals('recording', $document->getCurrentWorkflowStage());
|
||
|
||
// Stage 4: Reconciliation
|
||
$document->bank_reconciliation_id = 1;
|
||
$this->assertEquals('completed', $document->getCurrentWorkflowStage());
|
||
}
|
||
|
||
/** @test */
|
||
public function payment_completed_check_works()
|
||
{
|
||
$document = new FinanceDocument([
|
||
'payment_order_created_at' => now(),
|
||
'payment_verified_at' => now(),
|
||
'payment_executed_at' => null,
|
||
]);
|
||
|
||
$this->assertFalse($document->isPaymentCompleted());
|
||
|
||
$document->payment_executed_at = now();
|
||
$this->assertTrue($document->isPaymentCompleted());
|
||
}
|
||
|
||
/** @test */
|
||
public function recording_complete_check_works()
|
||
{
|
||
$document = new FinanceDocument([
|
||
'cashier_recorded_at' => null,
|
||
]);
|
||
|
||
$this->assertFalse($document->isRecordingComplete());
|
||
|
||
$document->cashier_recorded_at = now();
|
||
$this->assertTrue($document->isRecordingComplete());
|
||
}
|
||
|
||
/** @test */
|
||
public function reconciled_check_works()
|
||
{
|
||
$document = new FinanceDocument([
|
||
'bank_reconciliation_id' => null,
|
||
]);
|
||
|
||
$this->assertFalse($document->isReconciled());
|
||
|
||
$document->bank_reconciliation_id = 1;
|
||
$this->assertTrue($document->isReconciled());
|
||
}
|
||
|
||
/** @test */
|
||
public function request_type_text_is_correct()
|
||
{
|
||
$doc1 = new FinanceDocument(['request_type' => 'expense_reimbursement']);
|
||
$this->assertEquals('費用報銷', $doc1->getRequestTypeText());
|
||
|
||
$doc2 = new FinanceDocument(['request_type' => 'advance_payment']);
|
||
$this->assertEquals('預支款項', $doc2->getRequestTypeText());
|
||
|
||
$doc3 = new FinanceDocument(['request_type' => 'purchase_request']);
|
||
$this->assertEquals('採購申請', $doc3->getRequestTypeText());
|
||
|
||
$doc4 = new FinanceDocument(['request_type' => 'petty_cash']);
|
||
$this->assertEquals('零用金', $doc4->getRequestTypeText());
|
||
}
|
||
|
||
/** @test */
|
||
public function amount_tier_text_is_correct()
|
||
{
|
||
$small = new FinanceDocument(['amount_tier' => 'small']);
|
||
$this->assertEquals('小額(< 5000)', $small->getAmountTierText());
|
||
|
||
$medium = new FinanceDocument(['amount_tier' => 'medium']);
|
||
$this->assertEquals('中額(5000-50000)', $medium->getAmountTierText());
|
||
|
||
$large = new FinanceDocument(['amount_tier' => 'large']);
|
||
$this->assertEquals('大額(> 50000)', $large->getAmountTierText());
|
||
}
|
||
}
|