Files
GSB-Construction/tests/Feature/FinancialLifecycleE2ETest.php

155 lines
6.2 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Modules\ContractorManagement\Models\Contractor;
use Modules\FinancialManagement\Models\CashAdvance;
use Modules\FinancialManagement\Models\RetentionEntry;
use Modules\ProjectManagement\Models\Project;
use Spatie\Permission\Models\Role;
use Tests\TestCase;
class FinancialLifecycleE2ETest extends TestCase
{
use RefreshDatabase;
protected User $superAdmin;
protected User $projectManager;
protected User $contractorAdmin;
protected User $supervisor;
protected User $siteTech;
protected Project $project;
protected Contractor $contractor;
protected function setUp(): void
{
parent::setUp();
Role::firstOrCreate(['name' => 'Super Admin']);
Role::firstOrCreate(['name' => 'admin']);
Role::firstOrCreate(['name' => 'Project Manager']);
Role::firstOrCreate(['name' => 'Main Contractor Admin']);
Role::firstOrCreate(['name' => 'Construction Supervisor']);
Role::firstOrCreate(['name' => 'Site Technical']);
$permFinance = \Spatie\Permission\Models\Permission::firstOrCreate(['name' => 'finance.access']);
$permProjects = \Spatie\Permission\Models\Permission::firstOrCreate(['name' => 'projects.access']);
$this->contractor = Contractor::create([
'company_name' => 'Apex Buildworks',
'contact_person' => 'Sam Apex',
'email' => 'sam@apex.test',
'status' => 'active',
]);
$this->superAdmin = User::factory()->create(['user_type' => 'admin']);
$this->superAdmin->assignRole('Super Admin');
$this->superAdmin->givePermissionTo([$permFinance, $permProjects]);
$this->projectManager = User::factory()->create(['user_type' => 'employee']);
$this->projectManager->assignRole('Project Manager');
$this->projectManager->givePermissionTo([$permFinance, $permProjects]);
$this->contractorAdmin = User::factory()->create([
'user_type' => 'contractor',
'contractor_id' => $this->contractor->id,
]);
$this->contractorAdmin->assignRole('Main Contractor Admin');
$this->contractorAdmin->givePermissionTo([$permFinance, $permProjects]);
$this->supervisor = User::factory()->create([
'user_type' => 'employee',
'contractor_id' => $this->contractor->id,
]);
$this->supervisor->assignRole('Construction Supervisor');
$this->supervisor->givePermissionTo([$permFinance, $permProjects]);
$this->siteTech = User::factory()->create([
'user_type' => 'employee',
'contractor_id' => $this->contractor->id,
]);
$this->siteTech->assignRole('Site Technical');
$this->siteTech->givePermissionTo([$permFinance, $permProjects]);
$this->project = Project::create([
'name' => 'Skyline Tower Alpha',
'code' => 'PRJ-SKY-01',
'location' => 'Metropolis Central',
'status' => 'in_progress',
]);
$this->project->personnel()->attach($this->supervisor->id, ['role' => 'supervisor']);
$this->project->personnel()->attach($this->siteTech->id, ['role' => 'site_tech']);
$this->project->contractors()->attach($this->contractor->id, ['role' => 'general_contractor']);
}
public function test_complete_cash_advance_request_and_approval_flow(): void
{
// 1. Site Supervisor initiates Cash Advance
$response = $this->actingAs($this->supervisor)->post(route('cash-advances.store'), [
'project_ulid' => $this->project->ulid,
'amount' => 3500.00,
'reason' => 'Emergency fasteners and generator diesel top-up',
]);
$response->assertRedirect();
$this->assertDatabaseHas('cash_advances', [
'project_id' => $this->project->id,
'requested_by' => $this->supervisor->id,
'amount' => 3500.00,
'status' => 'pending',
]);
$advance = CashAdvance::where('project_id', $this->project->id)->first();
$this->assertNotNull($advance);
// 2. Site Technical attempts approval -> Must be blocked (redirect with error)
$unauthResp = $this->actingAs($this->siteTech)->patch(route('cash-advances.approve', $advance->ulid));
$unauthResp->assertSessionHas('error');
$this->assertEquals('pending', $advance->fresh()->status);
// 3. Project Manager approves Cash Advance
$approveResp = $this->actingAs($this->projectManager)->patch(route('cash-advances.approve', $advance->ulid));
$approveResp->assertRedirect();
$advance->refresh();
$this->assertEquals('approved', $advance->status);
$this->assertEquals($this->projectManager->id, $advance->approved_by);
}
public function test_complete_retention_release_and_contractor_confirmation_flow(): void
{
// 1. Initial debit entry for retention held
RetentionEntry::create([
'project_id' => $this->project->id,
'type' => 'debit',
'amount' => 50000.00,
'description' => 'Milestone 1 Retention Held',
'status' => 'posted',
]);
// 2. Executive creates credit entry for released retention payment sent
$retentionCredit = RetentionEntry::create([
'project_id' => $this->project->id,
'type' => 'credit',
'amount' => 50000.00,
'description' => 'Final Inspection Completed - Retention Settled',
'status' => 'payment_sent',
]);
// 3. Executive attempting to confirm contractor receipt -> Must be forbidden (403)
$selfConfirmResp = $this->actingAs($this->superAdmin)->patch(route('retention.confirm', $retentionCredit->ulid));
$selfConfirmResp->assertForbidden();
$this->assertEquals('payment_sent', $retentionCredit->fresh()->status);
// 4. Contractor Admin confirms receipt of released retention funds
$confirmResp = $this->actingAs($this->contractorAdmin)->patch(route('retention.confirm', $retentionCredit->ulid));
$confirmResp->assertRedirect();
$retentionCredit->refresh();
$this->assertEquals('paid', $retentionCredit->status);
}
}