250 lines
10 KiB
PHP
250 lines
10 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Modules\FinancialManagement\Models\CashAdvance;
|
|
use Modules\FinancialManagement\Models\FinancialInvoice;
|
|
use Modules\MaterialLogistics\Models\MaterialRequisition;
|
|
use Modules\MaterialLogistics\Models\PurchaseOrder;
|
|
use Modules\MaterialsCatalog\Models\MaterialItem;
|
|
use Modules\ProjectManagement\Models\Project;
|
|
|
|
use Spatie\Permission\Models\Permission;
|
|
use Spatie\Permission\Models\Role;
|
|
use Tests\TestCase;
|
|
|
|
class ComprehensiveSystemE2ETest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected User $superAdmin;
|
|
protected User $admin;
|
|
protected User $projectManager;
|
|
protected User $contractorAdmin;
|
|
protected User $supervisor;
|
|
protected User $siteTech;
|
|
protected Project $project;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
// 1. Initialize Roles & Permissions
|
|
$roles = [
|
|
'Super Admin',
|
|
'admin',
|
|
'Project Manager',
|
|
'Main Contractor Admin',
|
|
'Construction Supervisor',
|
|
'Site Technical',
|
|
];
|
|
|
|
foreach ($roles as $roleName) {
|
|
Role::firstOrCreate(['name' => $roleName]);
|
|
}
|
|
|
|
$permissions = [
|
|
'dashboard.access', 'projects.access', 'contractors.access',
|
|
'users.access', 'materials-catalog.access', 'inventory.access', 'finance.access',
|
|
'documents.access', 'approvals.access', 'roles.access', 'labors.access', 'equipments.access'
|
|
];
|
|
|
|
foreach ($permissions as $p) {
|
|
Permission::firstOrCreate(['name' => $p]);
|
|
}
|
|
|
|
// 2. Create Users & Assign Roles
|
|
$this->superAdmin = User::factory()->create(['user_type' => 'admin', 'name' => 'Super Admin User']);
|
|
$this->superAdmin->assignRole('Super Admin');
|
|
|
|
$this->admin = User::factory()->create(['user_type' => 'admin', 'name' => 'Admin User']);
|
|
$this->admin->assignRole('admin');
|
|
|
|
$this->projectManager = User::factory()->create(['user_type' => 'employee', 'name' => 'PM User']);
|
|
$this->projectManager->assignRole('Project Manager');
|
|
$this->projectManager->givePermissionTo([
|
|
'dashboard.access', 'projects.access', 'contractors.access',
|
|
'materials-catalog.access', 'inventory.access', 'finance.access', 'documents.access',
|
|
'approvals.access', 'labors.access', 'equipments.access'
|
|
]);
|
|
|
|
$this->contractorAdmin = User::factory()->create(['user_type' => 'contractor', 'name' => 'Contractor Admin']);
|
|
$this->contractorAdmin->assignRole('Main Contractor Admin');
|
|
$this->contractorAdmin->givePermissionTo([
|
|
'dashboard.access', 'projects.access', 'finance.access', 'documents.access'
|
|
]);
|
|
|
|
$this->supervisor = User::factory()->create(['user_type' => 'employee', 'name' => 'Supervisor User']);
|
|
$this->supervisor->assignRole('Construction Supervisor');
|
|
$this->supervisor->givePermissionTo([
|
|
'dashboard.access', 'projects.access', 'inventory.access', 'materials-catalog.access', 'documents.access'
|
|
]);
|
|
|
|
$this->siteTech = User::factory()->create(['user_type' => 'employee', 'name' => 'Site Technical User']);
|
|
$this->siteTech->assignRole('Site Technical');
|
|
$this->siteTech->givePermissionTo([
|
|
'dashboard.access', 'projects.access', 'inventory.access', 'documents.access'
|
|
]);
|
|
|
|
$this->project = Project::factory()->create([
|
|
'current_wizard_step' => 7,
|
|
]);
|
|
$this->project->personnel()->attach($this->projectManager->id, ['role' => 'pm']);
|
|
$this->project->personnel()->attach($this->supervisor->id, ['role' => 'supervisor']);
|
|
}
|
|
|
|
/** @test */
|
|
public function test_e2e_all_roles_page_accessibility_across_modules(): void
|
|
{
|
|
// 1. Super Admin Page Traversal
|
|
$this->actingAs($this->superAdmin);
|
|
$routes = [
|
|
'dashboard', 'projects.index', 'contractors.index',
|
|
'users.index', 'materials-catalog.items.index', 'inventory.index',
|
|
'labors.index', 'equipments.index', 'resources.index', 'finance.index',
|
|
'retention.index', 'cash-advances.index', 'documents.index', 'approvals.index'
|
|
];
|
|
|
|
foreach ($routes as $route) {
|
|
$response = $this->get(route($route));
|
|
$response->assertStatus(200);
|
|
}
|
|
|
|
// 2. Project Manager Page Traversal
|
|
$this->actingAs($this->projectManager);
|
|
foreach (['dashboard', 'projects.index', 'inventory.index', 'finance.index', 'approvals.index'] as $route) {
|
|
$response = $this->get(route($route));
|
|
$response->assertStatus(200);
|
|
}
|
|
|
|
// 3. Contractor Admin Page Traversal
|
|
$this->actingAs($this->contractorAdmin);
|
|
foreach (['dashboard', 'projects.index', 'finance.index'] as $route) {
|
|
$response = $this->get(route($route));
|
|
$response->assertStatus(200);
|
|
}
|
|
|
|
// 4. Construction Supervisor Page Traversal
|
|
$this->actingAs($this->supervisor);
|
|
foreach (['dashboard', 'projects.index', 'inventory.index', 'cash-advances.index'] as $route) {
|
|
$response = $this->get(route($route));
|
|
$response->assertStatus(200);
|
|
}
|
|
|
|
// 5. Site Technical Page Traversal
|
|
$this->actingAs($this->siteTech);
|
|
foreach (['dashboard', 'projects.index', 'inventory.index', 'cash-advances.index'] as $route) {
|
|
$response = $this->get(route($route));
|
|
$response->assertStatus(200);
|
|
}
|
|
}
|
|
|
|
/** @test */
|
|
public function test_e2e_cash_advance_full_lifecycle_and_security_rules(): void
|
|
{
|
|
// Step 1: Supervisor creates cash advance request
|
|
$this->actingAs($this->supervisor);
|
|
$createResponse = $this->post(route('cash-advances.store'), [
|
|
'project_ulid' => $this->project->ulid,
|
|
'amount' => 750.50,
|
|
'reason' => 'Emergency site safety gear purchase',
|
|
]);
|
|
$createResponse->assertSessionHasNoErrors();
|
|
|
|
$cashAdvance = CashAdvance::where('amount', 750.50)->firstOrFail();
|
|
$this->assertEquals('pending', $cashAdvance->status);
|
|
|
|
// Step 2: Site Technical attempts to approve (Unauthorized/Blocked logic)
|
|
$this->actingAs($this->siteTech);
|
|
$this->patch(route('cash-advances.approve', $cashAdvance->ulid));
|
|
$this->assertEquals('pending', $cashAdvance->fresh()->status);
|
|
|
|
// Step 3: PM creates request and attempts self-approval (Blocked rule)
|
|
$this->actingAs($this->projectManager);
|
|
$pmAdvance = CashAdvance::create([
|
|
'project_id' => $this->project->id,
|
|
'amount' => 1200.00,
|
|
'reason' => 'PM site inspection travel',
|
|
'status' => 'pending',
|
|
'requested_by' => $this->projectManager->id,
|
|
]);
|
|
$this->patch(route('cash-advances.approve', $pmAdvance->ulid));
|
|
$this->assertEquals('pending', $pmAdvance->fresh()->status);
|
|
|
|
// Step 4: PM approves Supervisor's request
|
|
$this->patch(route('cash-advances.approve', $cashAdvance->ulid));
|
|
$this->assertEquals('approved', $cashAdvance->fresh()->status);
|
|
$this->assertEquals($this->projectManager->id, $cashAdvance->fresh()->approved_by);
|
|
|
|
// Step 5: Admin approves PM's self-requested advance
|
|
$this->actingAs($this->admin);
|
|
$this->patch(route('cash-advances.approve', $pmAdvance->ulid));
|
|
$this->assertEquals('approved', $pmAdvance->fresh()->status);
|
|
}
|
|
|
|
/** @test */
|
|
public function test_e2e_financial_invoice_and_retention_flow(): void
|
|
{
|
|
// Step 1: Contractor submits invoice
|
|
$invoice = FinancialInvoice::create([
|
|
'project_id' => $this->project->id,
|
|
'invoice_number' => 'INV-E2E-101',
|
|
'status' => 'submitted',
|
|
'subtotal' => 200000.00,
|
|
'retention_rate' => 10,
|
|
'retention_amount' => 20000.00,
|
|
'total_amount' => 180000.00,
|
|
'billed_percentage' => 20,
|
|
'invoice_date' => now(),
|
|
]);
|
|
|
|
// Step 2: Supervisor attempts invoice approval (Supervisor is restricted from final invoice approval)
|
|
$this->actingAs($this->supervisor);
|
|
$this->patch(route('finance.approve', $invoice->ulid));
|
|
$this->assertEquals(\Modules\FinancialManagement\Enums\InvoiceStatus::Submitted, $invoice->fresh()->status);
|
|
|
|
// Step 3: Super Admin approves invoice
|
|
$this->actingAs($this->superAdmin);
|
|
$response = $this->patch(route('finance.approve', $invoice->ulid));
|
|
$response->assertSessionHasNoErrors();
|
|
$this->assertEquals(\Modules\FinancialManagement\Enums\InvoiceStatus::Approved, $invoice->fresh()->status);
|
|
}
|
|
|
|
/** @test */
|
|
public function test_e2e_project_wizard_flow_integrity(): void
|
|
{
|
|
$this->actingAs($this->admin);
|
|
|
|
// Step 1: Create Initial Draft Project
|
|
$postResponse = $this->post(route('projects.store'), [
|
|
'name' => 'E2E Mega Tower Construction',
|
|
'project_type' => 'standard',
|
|
'contract_value' => 5000000,
|
|
'start_date' => now()->addDays(5)->format('Y-m-d'),
|
|
'target_end_date' => now()->addYear()->format('Y-m-d'),
|
|
]);
|
|
$postResponse->assertSessionHasNoErrors();
|
|
|
|
$project = Project::where('name', 'E2E Mega Tower Construction')->firstOrFail();
|
|
$this->assertEquals(2, $project->current_wizard_step);
|
|
|
|
// Step 2: Submit WBS Tasks
|
|
$taskResponse = $this->post(route('projects.wizard.tasks', $project->ulid), [
|
|
'milestones' => [
|
|
[
|
|
'name' => 'Foundation & Earthworks',
|
|
'weight_percentage' => 25,
|
|
'tasks' => [
|
|
['name' => 'Excavation & Site Prep', 'weight' => 10],
|
|
['name' => 'Concrete Pouring', 'weight' => 15],
|
|
],
|
|
]
|
|
],
|
|
]);
|
|
$taskResponse->assertSessionHasNoErrors();
|
|
$this->assertGreaterThanOrEqual(2, $project->fresh()->current_wizard_step);
|
|
}
|
|
}
|