327 lines
12 KiB
PHP
327 lines
12 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Modules\FinancialManagement\Models\CashAdvance;
|
|
use Modules\MaterialLogistics\Models\MaterialRequisition;
|
|
use Modules\MaterialLogistics\Models\PurchaseOrder;
|
|
use Modules\ProjectManagement\Models\Project;
|
|
use Spatie\Permission\Models\Role;
|
|
use Tests\TestCase;
|
|
|
|
class RoleBasedActionTest 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();
|
|
|
|
// Create roles
|
|
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']);
|
|
|
|
// Create test users
|
|
$this->superAdmin = User::factory()->create(['user_type' => 'admin']);
|
|
$this->superAdmin->assignRole('Super Admin');
|
|
|
|
$this->admin = User::factory()->create(['user_type' => 'admin']);
|
|
$this->admin->assignRole('admin');
|
|
|
|
$this->projectManager = User::factory()->create(['user_type' => 'employee']);
|
|
$this->projectManager->assignRole('Project Manager');
|
|
|
|
$this->contractorAdmin = User::factory()->create(['user_type' => 'contractor']);
|
|
$this->contractorAdmin->assignRole('Main Contractor Admin');
|
|
|
|
$this->supervisor = User::factory()->create(['user_type' => 'employee']);
|
|
$this->supervisor->assignRole('Construction Supervisor');
|
|
|
|
$this->siteTech = User::factory()->create(['user_type' => 'employee']);
|
|
$this->siteTech->assignRole('Site Technical');
|
|
|
|
$this->project = Project::factory()->create();
|
|
}
|
|
|
|
/** @test */
|
|
public function supervisor_can_create_cash_advance_request()
|
|
{
|
|
$response = $this->actingAs($this->supervisor)
|
|
->post(route('cash-advances.store'), [
|
|
'project_ulid' => $this->project->ulid,
|
|
'amount' => 500.00,
|
|
'reason' => 'Petty cash for emergency site fasteners',
|
|
]);
|
|
|
|
$response->assertSessionHasNoErrors();
|
|
$this->assertDatabaseHas('cash_advances', [
|
|
'amount' => 500.00,
|
|
'status' => 'pending',
|
|
'requested_by' => $this->supervisor->id,
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function project_manager_can_approve_cash_advance_request()
|
|
{
|
|
$cashAdvance = CashAdvance::create([
|
|
'project_id' => $this->project->id,
|
|
'amount' => 500.00,
|
|
'reason' => 'Emergency fasteners',
|
|
'status' => 'pending',
|
|
'requested_by' => $this->supervisor->id,
|
|
]);
|
|
|
|
$response = $this->actingAs($this->projectManager)
|
|
->patch(route('cash-advances.approve', $cashAdvance->ulid));
|
|
|
|
$response->assertSessionHasNoErrors();
|
|
$this->assertDatabaseHas('cash_advances', [
|
|
'id' => $cashAdvance->id,
|
|
'status' => 'approved',
|
|
'approved_by' => $this->projectManager->id,
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function site_technical_cannot_approve_cash_advances()
|
|
{
|
|
$cashAdvance = CashAdvance::create([
|
|
'project_id' => $this->project->id,
|
|
'amount' => 500.00,
|
|
'reason' => 'Emergency fasteners',
|
|
'status' => 'pending',
|
|
'requested_by' => $this->supervisor->id,
|
|
]);
|
|
|
|
// Attempting approval as Site Technical should not approve
|
|
$response = $this->actingAs($this->siteTech)
|
|
->patch(route('cash-advances.approve', $cashAdvance->ulid));
|
|
|
|
// Status remains pending
|
|
$this->assertDatabaseHas('cash_advances', [
|
|
'id' => $cashAdvance->id,
|
|
'status' => 'pending',
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function admin_and_super_admin_can_approve_cash_advances()
|
|
{
|
|
$cashAdvance = CashAdvance::create([
|
|
'project_id' => $this->project->id,
|
|
'amount' => 300.00,
|
|
'reason' => 'Local refreshments',
|
|
'status' => 'pending',
|
|
'requested_by' => $this->supervisor->id,
|
|
]);
|
|
|
|
$response = $this->actingAs($this->superAdmin)
|
|
->patch(route('cash-advances.approve', $cashAdvance->ulid));
|
|
|
|
$response->assertSessionHasNoErrors();
|
|
$this->assertDatabaseHas('cash_advances', [
|
|
'id' => $cashAdvance->id,
|
|
'status' => 'approved',
|
|
'approved_by' => $this->superAdmin->id,
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function user_cannot_self_approve_cash_advance()
|
|
{
|
|
$cashAdvance = CashAdvance::create([
|
|
'project_id' => $this->project->id,
|
|
'amount' => 400.00,
|
|
'reason' => 'Self request test',
|
|
'status' => 'pending',
|
|
'requested_by' => $this->projectManager->id,
|
|
]);
|
|
|
|
$response = $this->actingAs($this->projectManager)
|
|
->patch(route('cash-advances.approve', $cashAdvance->ulid));
|
|
|
|
$this->assertDatabaseHas('cash_advances', [
|
|
'id' => $cashAdvance->id,
|
|
'status' => 'pending',
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function project_manager_cannot_approve_submitted_invoice()
|
|
{
|
|
$invoice = \Modules\FinancialManagement\Models\FinancialInvoice::create([
|
|
'project_id' => $this->project->id,
|
|
'invoice_number' => 'INV-TEST-001',
|
|
'status' => 'submitted',
|
|
'subtotal' => 100000,
|
|
'retention_rate' => 10,
|
|
'retention_amount' => 10000,
|
|
'total_amount' => 90000,
|
|
'billed_percentage' => 10,
|
|
'invoice_date' => now(),
|
|
]);
|
|
|
|
$response = $this->actingAs($this->projectManager)
|
|
->patch(route('finance.approve', $invoice->ulid));
|
|
|
|
$this->assertDatabaseHas('financial_invoices', [
|
|
'id' => $invoice->id,
|
|
'status' => 'submitted',
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function super_admin_and_admin_can_approve_submitted_invoice()
|
|
{
|
|
$invoice = \Modules\FinancialManagement\Models\FinancialInvoice::create([
|
|
'project_id' => $this->project->id,
|
|
'invoice_number' => 'INV-TEST-002',
|
|
'status' => 'submitted',
|
|
'subtotal' => 100000,
|
|
'retention_rate' => 10,
|
|
'retention_amount' => 10000,
|
|
'total_amount' => 90000,
|
|
'billed_percentage' => 10,
|
|
'invoice_date' => now(),
|
|
]);
|
|
|
|
$response = $this->actingAs($this->superAdmin)
|
|
->patch(route('finance.approve', $invoice->ulid));
|
|
|
|
$response->assertSessionHasNoErrors();
|
|
$this->assertDatabaseHas('financial_invoices', [
|
|
'id' => $invoice->id,
|
|
'status' => 'approved',
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function all_roles_can_access_their_permissioned_pages_without_errors()
|
|
{
|
|
// Give permissions to roles
|
|
$permission = \Spatie\Permission\Models\Permission::firstOrCreate(['name' => 'finance.access']);
|
|
$this->projectManager->givePermissionTo($permission);
|
|
|
|
// Test PM navigation
|
|
$this->actingAs($this->projectManager)
|
|
->get(route('finance.index'))
|
|
->assertStatus(200);
|
|
|
|
$this->actingAs($this->projectManager)
|
|
->get(route('retention.index'))
|
|
->assertStatus(200);
|
|
|
|
// Test Super Admin navigation
|
|
$this->actingAs($this->superAdmin)
|
|
->get(route('finance.index'))
|
|
->assertStatus(200);
|
|
|
|
$this->actingAs($this->superAdmin)
|
|
->get(route('approvals.index'))
|
|
->assertStatus(200);
|
|
}
|
|
|
|
/** @test */
|
|
public function test_comprehensive_all_roles_all_modules_page_access_and_crud_integrity(): void
|
|
{
|
|
// Grant permissions matching RolesPermissionsDatabaseSeeder
|
|
$permissions = [
|
|
'dashboard.access', 'projects.access', 'contractors.access', 'bidding.access',
|
|
'users.access', 'materials-catalog.access', 'inventory.access', 'finance.access',
|
|
'documents.access', 'approvals.access', 'roles.access', 'labors.access', 'equipments.access'
|
|
];
|
|
foreach ($permissions as $p) {
|
|
\Spatie\Permission\Models\Permission::firstOrCreate(['name' => $p]);
|
|
}
|
|
|
|
$this->projectManager->syncPermissions([
|
|
'dashboard.access', 'projects.access', 'contractors.access', 'bidding.access',
|
|
'materials-catalog.access', 'inventory.access', 'finance.access', 'documents.access',
|
|
'approvals.access', 'labors.access', 'equipments.access'
|
|
]);
|
|
|
|
$this->contractorAdmin->syncPermissions([
|
|
'dashboard.access', 'projects.access', 'bidding.access', 'finance.access', 'documents.access'
|
|
]);
|
|
|
|
$this->supervisor->syncPermissions([
|
|
'dashboard.access', 'projects.access', 'inventory.access', 'materials-catalog.access', 'documents.access'
|
|
]);
|
|
|
|
$this->siteTech->syncPermissions([
|
|
'dashboard.access', 'projects.access', 'inventory.access', 'documents.access'
|
|
]);
|
|
|
|
// 1. Super Admin Page Access & CRUD Across All Modules
|
|
$this->actingAs($this->superAdmin);
|
|
|
|
$superAdminRoutes = [
|
|
'dashboard', 'projects.index', 'contractors.index',
|
|
'bids.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', 'rolespermissions.index', 'purchase-orders.index', 'requisitions.index'
|
|
];
|
|
|
|
foreach ($superAdminRoutes as $routeName) {
|
|
$response = $this->get(route($routeName));
|
|
$response->assertStatus(200);
|
|
}
|
|
|
|
// 2. Project Manager Page Access Across Permissioned Modules
|
|
$this->actingAs($this->projectManager);
|
|
$pmRoutes = [
|
|
'dashboard', 'projects.index', 'materials-catalog.items.index', 'inventory.index',
|
|
'labors.index', 'equipments.index', 'finance.index', 'retention.index',
|
|
'documents.index', 'approvals.index'
|
|
];
|
|
|
|
foreach ($pmRoutes as $routeName) {
|
|
$response = $this->get(route($routeName));
|
|
$response->assertStatus(200);
|
|
}
|
|
|
|
// 3. Contractor Admin Page Access
|
|
$this->actingAs($this->contractorAdmin);
|
|
$contractorRoutes = ['dashboard', 'projects.index', 'bids.index', 'finance.index', 'documents.index'];
|
|
|
|
foreach ($contractorRoutes as $routeName) {
|
|
$response = $this->get(route($routeName));
|
|
$response->assertStatus(200);
|
|
}
|
|
|
|
// 4. Construction Supervisor Page Access
|
|
$this->actingAs($this->supervisor);
|
|
$supervisorRoutes = ['dashboard', 'projects.index', 'inventory.index', 'materials-catalog.items.index', 'documents.index', 'cash-advances.index'];
|
|
|
|
foreach ($supervisorRoutes as $routeName) {
|
|
$response = $this->get(route($routeName));
|
|
$response->assertStatus(200);
|
|
}
|
|
|
|
// 5. Site Technical Page Access
|
|
$this->actingAs($this->siteTech);
|
|
$siteTechRoutes = ['dashboard', 'projects.index', 'inventory.index', 'documents.index', 'cash-advances.index'];
|
|
|
|
foreach ($siteTechRoutes as $routeName) {
|
|
$response = $this->get(route($routeName));
|
|
$response->assertStatus(200);
|
|
}
|
|
}
|
|
}
|