301 lines
10 KiB
PHP
301 lines
10 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit;
|
|
|
|
use App\Models\Budget;
|
|
use App\Models\BudgetItem;
|
|
use App\Models\ChartOfAccount;
|
|
use App\Models\Transaction;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
class BudgetTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->artisan('db:seed', ['--class' => 'RoleSeeder']);
|
|
$this->artisan('db:seed', ['--class' => 'ChartOfAccountSeeder']);
|
|
}
|
|
|
|
public function test_budget_belongs_to_created_by_user(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$budget = Budget::factory()->create(['created_by_user_id' => $user->id]);
|
|
|
|
$this->assertInstanceOf(User::class, $budget->createdBy);
|
|
$this->assertEquals($user->id, $budget->createdBy->id);
|
|
}
|
|
|
|
public function test_budget_belongs_to_approved_by_user(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$budget = Budget::factory()->create(['approved_by_user_id' => $user->id]);
|
|
|
|
$this->assertInstanceOf(User::class, $budget->approvedBy);
|
|
$this->assertEquals($user->id, $budget->approvedBy->id);
|
|
}
|
|
|
|
public function test_budget_has_many_budget_items(): void
|
|
{
|
|
$budget = Budget::factory()->create();
|
|
$account1 = ChartOfAccount::first();
|
|
$account2 = ChartOfAccount::skip(1)->first();
|
|
|
|
$item1 = BudgetItem::factory()->create([
|
|
'budget_id' => $budget->id,
|
|
'chart_of_account_id' => $account1->id,
|
|
]);
|
|
$item2 = BudgetItem::factory()->create([
|
|
'budget_id' => $budget->id,
|
|
'chart_of_account_id' => $account2->id,
|
|
]);
|
|
|
|
$this->assertCount(2, $budget->budgetItems);
|
|
}
|
|
|
|
public function test_is_draft_returns_true_when_status_is_draft(): void
|
|
{
|
|
$budget = Budget::factory()->create(['status' => Budget::STATUS_DRAFT]);
|
|
$this->assertTrue($budget->isDraft());
|
|
}
|
|
|
|
public function test_is_approved_returns_true_when_status_is_approved(): void
|
|
{
|
|
$budget = Budget::factory()->create(['status' => Budget::STATUS_APPROVED]);
|
|
$this->assertTrue($budget->isApproved());
|
|
}
|
|
|
|
public function test_is_active_returns_true_when_status_is_active(): void
|
|
{
|
|
$budget = Budget::factory()->create(['status' => Budget::STATUS_ACTIVE]);
|
|
$this->assertTrue($budget->isActive());
|
|
}
|
|
|
|
public function test_is_closed_returns_true_when_status_is_closed(): void
|
|
{
|
|
$budget = Budget::factory()->create(['status' => Budget::STATUS_CLOSED]);
|
|
$this->assertTrue($budget->isClosed());
|
|
}
|
|
|
|
public function test_can_be_edited_validates_correctly(): void
|
|
{
|
|
$draftBudget = Budget::factory()->create(['status' => Budget::STATUS_DRAFT]);
|
|
$this->assertTrue($draftBudget->canBeEdited());
|
|
|
|
$submittedBudget = Budget::factory()->create(['status' => Budget::STATUS_SUBMITTED]);
|
|
$this->assertTrue($submittedBudget->canBeEdited());
|
|
|
|
$activeBudget = Budget::factory()->create(['status' => Budget::STATUS_ACTIVE]);
|
|
$this->assertFalse($activeBudget->canBeEdited());
|
|
|
|
$closedBudget = Budget::factory()->create(['status' => Budget::STATUS_CLOSED]);
|
|
$this->assertFalse($closedBudget->canBeEdited());
|
|
}
|
|
|
|
public function test_can_be_approved_validates_correctly(): void
|
|
{
|
|
$submittedBudget = Budget::factory()->create(['status' => Budget::STATUS_SUBMITTED]);
|
|
$this->assertTrue($submittedBudget->canBeApproved());
|
|
|
|
$draftBudget = Budget::factory()->create(['status' => Budget::STATUS_DRAFT]);
|
|
$this->assertFalse($draftBudget->canBeApproved());
|
|
|
|
$approvedBudget = Budget::factory()->create(['status' => Budget::STATUS_APPROVED]);
|
|
$this->assertFalse($approvedBudget->canBeApproved());
|
|
}
|
|
|
|
public function test_total_budgeted_income_calculation(): void
|
|
{
|
|
$budget = Budget::factory()->create();
|
|
$incomeAccount = ChartOfAccount::where('account_type', ChartOfAccount::TYPE_INCOME)->first();
|
|
|
|
if ($incomeAccount) {
|
|
BudgetItem::factory()->create([
|
|
'budget_id' => $budget->id,
|
|
'chart_of_account_id' => $incomeAccount->id,
|
|
'budgeted_amount' => 10000,
|
|
]);
|
|
BudgetItem::factory()->create([
|
|
'budget_id' => $budget->id,
|
|
'chart_of_account_id' => $incomeAccount->id,
|
|
'budgeted_amount' => 5000,
|
|
]);
|
|
|
|
$this->assertEquals(15000, $budget->total_budgeted_income);
|
|
} else {
|
|
$this->assertTrue(true, 'No income account available for test');
|
|
}
|
|
}
|
|
|
|
public function test_total_budgeted_expense_calculation(): void
|
|
{
|
|
$budget = Budget::factory()->create();
|
|
$expenseAccount = ChartOfAccount::where('account_type', ChartOfAccount::TYPE_EXPENSE)->first();
|
|
|
|
if ($expenseAccount) {
|
|
BudgetItem::factory()->create([
|
|
'budget_id' => $budget->id,
|
|
'chart_of_account_id' => $expenseAccount->id,
|
|
'budgeted_amount' => 8000,
|
|
]);
|
|
BudgetItem::factory()->create([
|
|
'budget_id' => $budget->id,
|
|
'chart_of_account_id' => $expenseAccount->id,
|
|
'budgeted_amount' => 3000,
|
|
]);
|
|
|
|
$this->assertEquals(11000, $budget->total_budgeted_expense);
|
|
} else {
|
|
$this->assertTrue(true, 'No expense account available for test');
|
|
}
|
|
}
|
|
|
|
public function test_total_actual_income_calculation(): void
|
|
{
|
|
$budget = Budget::factory()->create();
|
|
$incomeAccount = ChartOfAccount::where('account_type', ChartOfAccount::TYPE_INCOME)->first();
|
|
|
|
if ($incomeAccount) {
|
|
BudgetItem::factory()->create([
|
|
'budget_id' => $budget->id,
|
|
'chart_of_account_id' => $incomeAccount->id,
|
|
'budgeted_amount' => 10000,
|
|
'actual_amount' => 12000,
|
|
]);
|
|
BudgetItem::factory()->create([
|
|
'budget_id' => $budget->id,
|
|
'chart_of_account_id' => $incomeAccount->id,
|
|
'budgeted_amount' => 5000,
|
|
'actual_amount' => 4500,
|
|
]);
|
|
|
|
$this->assertEquals(16500, $budget->total_actual_income);
|
|
} else {
|
|
$this->assertTrue(true, 'No income account available for test');
|
|
}
|
|
}
|
|
|
|
public function test_total_actual_expense_calculation(): void
|
|
{
|
|
$budget = Budget::factory()->create();
|
|
$expenseAccount = ChartOfAccount::where('account_type', ChartOfAccount::TYPE_EXPENSE)->first();
|
|
|
|
if ($expenseAccount) {
|
|
BudgetItem::factory()->create([
|
|
'budget_id' => $budget->id,
|
|
'chart_of_account_id' => $expenseAccount->id,
|
|
'budgeted_amount' => 8000,
|
|
'actual_amount' => 7500,
|
|
]);
|
|
BudgetItem::factory()->create([
|
|
'budget_id' => $budget->id,
|
|
'chart_of_account_id' => $expenseAccount->id,
|
|
'budgeted_amount' => 3000,
|
|
'actual_amount' => 3200,
|
|
]);
|
|
|
|
$this->assertEquals(10700, $budget->total_actual_expense);
|
|
} else {
|
|
$this->assertTrue(true, 'No expense account available for test');
|
|
}
|
|
}
|
|
|
|
public function test_budget_item_variance_calculation(): void
|
|
{
|
|
$account = ChartOfAccount::first();
|
|
$budgetItem = BudgetItem::factory()->create([
|
|
'chart_of_account_id' => $account->id,
|
|
'budgeted_amount' => 10000,
|
|
'actual_amount' => 8500,
|
|
]);
|
|
|
|
$this->assertEquals(-1500, $budgetItem->variance);
|
|
}
|
|
|
|
public function test_budget_item_variance_percentage_calculation(): void
|
|
{
|
|
$account = ChartOfAccount::first();
|
|
$budgetItem = BudgetItem::factory()->create([
|
|
'chart_of_account_id' => $account->id,
|
|
'budgeted_amount' => 10000,
|
|
'actual_amount' => 8500,
|
|
]);
|
|
|
|
$this->assertEquals(-15.0, $budgetItem->variance_percentage);
|
|
}
|
|
|
|
public function test_budget_item_remaining_budget_calculation(): void
|
|
{
|
|
$account = ChartOfAccount::first();
|
|
$budgetItem = BudgetItem::factory()->create([
|
|
'chart_of_account_id' => $account->id,
|
|
'budgeted_amount' => 10000,
|
|
'actual_amount' => 6000,
|
|
]);
|
|
|
|
$this->assertEquals(4000, $budgetItem->remaining_budget);
|
|
}
|
|
|
|
public function test_budget_item_is_over_budget_detection(): void
|
|
{
|
|
$account = ChartOfAccount::first();
|
|
|
|
$overBudgetItem = BudgetItem::factory()->create([
|
|
'chart_of_account_id' => $account->id,
|
|
'budgeted_amount' => 10000,
|
|
'actual_amount' => 12000,
|
|
]);
|
|
$this->assertTrue($overBudgetItem->isOverBudget());
|
|
|
|
$underBudgetItem = BudgetItem::factory()->create([
|
|
'chart_of_account_id' => $account->id,
|
|
'budgeted_amount' => 10000,
|
|
'actual_amount' => 8000,
|
|
]);
|
|
$this->assertFalse($underBudgetItem->isOverBudget());
|
|
}
|
|
|
|
public function test_budget_item_utilization_percentage_calculation(): void
|
|
{
|
|
$account = ChartOfAccount::first();
|
|
$budgetItem = BudgetItem::factory()->create([
|
|
'chart_of_account_id' => $account->id,
|
|
'budgeted_amount' => 10000,
|
|
'actual_amount' => 7500,
|
|
]);
|
|
|
|
$this->assertEquals(75.0, $budgetItem->utilization_percentage);
|
|
}
|
|
|
|
public function test_budget_workflow_sequence(): void
|
|
{
|
|
$budget = Budget::factory()->create(['status' => Budget::STATUS_DRAFT]);
|
|
|
|
// Draft can be edited
|
|
$this->assertTrue($budget->canBeEdited());
|
|
$this->assertFalse($budget->canBeApproved());
|
|
|
|
// Submitted can be edited and approved
|
|
$budget->status = Budget::STATUS_SUBMITTED;
|
|
$this->assertTrue($budget->canBeEdited());
|
|
$this->assertTrue($budget->canBeApproved());
|
|
|
|
// Approved cannot be edited
|
|
$budget->status = Budget::STATUS_APPROVED;
|
|
$this->assertFalse($budget->canBeEdited());
|
|
$this->assertFalse($budget->canBeApproved());
|
|
|
|
// Active cannot be edited
|
|
$budget->status = Budget::STATUS_ACTIVE;
|
|
$this->assertFalse($budget->canBeEdited());
|
|
|
|
// Closed cannot be edited
|
|
$budget->status = Budget::STATUS_CLOSED;
|
|
$this->assertFalse($budget->canBeEdited());
|
|
}
|
|
}
|