- Restrict overtime applications query, submission, approval, and attendance details to assigned branch - Restrict attendance regularization listing, statistics, and dropdowns to assigned branch - Restrict 13th month pay batch generation, historical runs, entry updates, approvals, and CSV exports to assigned branch - Add feature tests in BranchScopedAccessTest for overtime and 13th month isolation
474 lines
16 KiB
PHP
474 lines
16 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use Tests\TestCase;
|
|
use App\Models\User;
|
|
use App\Models\Branch;
|
|
use App\Models\Department;
|
|
use App\Models\Employee;
|
|
use App\Models\PayrollRun;
|
|
use App\Models\PayrollEntry;
|
|
use App\Models\Payslip;
|
|
use App\Models\OvertimeApplication;
|
|
use App\Models\ThirteenthMonthRun;
|
|
use App\Models\ThirteenthMonthEntry;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Spatie\Permission\Models\Role;
|
|
use Spatie\Permission\Models\Permission;
|
|
|
|
class BranchScopedAccessTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
// Ensure permissions exist
|
|
Permission::firstOrCreate(['name' => 'manage-branches']);
|
|
Permission::firstOrCreate(['name' => 'manage-any-branches']);
|
|
Permission::firstOrCreate(['name' => 'manage-departments']);
|
|
Permission::firstOrCreate(['name' => 'manage-any-departments']);
|
|
Permission::firstOrCreate(['name' => 'manage-employees']);
|
|
Permission::firstOrCreate(['name' => 'manage-any-employees']);
|
|
Permission::firstOrCreate(['name' => 'manage-payslips']);
|
|
Permission::firstOrCreate(['name' => 'manage-any-payslips']);
|
|
Permission::firstOrCreate(['name' => 'manage-payroll-runs']);
|
|
}
|
|
|
|
public function test_auth_branch_id_helper_resolution()
|
|
{
|
|
$company = User::create([
|
|
'name' => 'Company Admin',
|
|
'email' => 'company_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'company',
|
|
]);
|
|
|
|
$superadmin = User::create([
|
|
'name' => 'Super Admin',
|
|
'email' => 'admin_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'superadmin',
|
|
]);
|
|
|
|
$branch = Branch::create([
|
|
'name' => 'Branch Alpha',
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
$userDirectBranch = User::create([
|
|
'name' => 'Branch User',
|
|
'email' => 'hr_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'hr',
|
|
'branch_id' => $branch->id,
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
$this->actingAs($company);
|
|
$this->assertNull(authBranchId());
|
|
|
|
$this->actingAs($superadmin);
|
|
$this->assertNull(authBranchId());
|
|
|
|
$this->actingAs($userDirectBranch);
|
|
$this->assertEquals($branch->id, authBranchId());
|
|
}
|
|
|
|
public function test_branch_scoped_user_only_sees_assigned_branch()
|
|
{
|
|
$company = User::create([
|
|
'name' => 'Company Admin',
|
|
'email' => 'company_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'company',
|
|
]);
|
|
$company->givePermissionTo('manage-branches');
|
|
$company->givePermissionTo('manage-any-branches');
|
|
|
|
$branchA = Branch::create([
|
|
'name' => 'Branch Alpha',
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
$branchB = Branch::create([
|
|
'name' => 'Branch Beta',
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
$hrUser = User::create([
|
|
'name' => 'HR User Branch A',
|
|
'email' => 'hr_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'hr',
|
|
'branch_id' => $branchA->id,
|
|
'created_by' => $company->id,
|
|
]);
|
|
$hrUser->givePermissionTo('manage-branches');
|
|
$hrUser->givePermissionTo('manage-any-branches');
|
|
|
|
// Test HR accessing branches index
|
|
$response = $this->actingAs($hrUser)->get(route('hr.branches.index'));
|
|
$response->assertStatus(200);
|
|
$response->assertSee('Branch Alpha');
|
|
$response->assertDontSee('Branch Beta');
|
|
|
|
// Test Company Admin accessing branches index
|
|
$companyResponse = $this->actingAs($company)->get(route('hr.branches.index'));
|
|
$companyResponse->assertStatus(200);
|
|
$companyResponse->assertSee('Branch Alpha');
|
|
$companyResponse->assertSee('Branch Beta');
|
|
}
|
|
|
|
public function test_branch_scoped_user_only_sees_assigned_branch_departments_and_employees()
|
|
{
|
|
$company = User::create([
|
|
'name' => 'Company Admin',
|
|
'email' => 'company_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'company',
|
|
]);
|
|
|
|
$branchA = Branch::create([
|
|
'name' => 'Branch Alpha',
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
$branchB = Branch::create([
|
|
'name' => 'Branch Beta',
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
$deptA = Department::create([
|
|
'name' => 'Dept Alpha Finance',
|
|
'branch_id' => $branchA->id,
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
$deptB = Department::create([
|
|
'name' => 'Dept Beta Logistics',
|
|
'branch_id' => $branchB->id,
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
// Employee in Branch A
|
|
$userA = User::create([
|
|
'name' => 'Employee Alpha User',
|
|
'email' => 'alpha_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'employee',
|
|
'created_by' => $company->id,
|
|
]);
|
|
Employee::create([
|
|
'user_id' => $userA->id,
|
|
'employee_id' => 'EMP-A-' . rand(100, 999),
|
|
'branch_id' => $branchA->id,
|
|
'department_id' => $deptA->id,
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
// Employee in Branch B
|
|
$userB = User::create([
|
|
'name' => 'Employee Beta User',
|
|
'email' => 'beta_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'employee',
|
|
'created_by' => $company->id,
|
|
]);
|
|
Employee::create([
|
|
'user_id' => $userB->id,
|
|
'employee_id' => 'EMP-B-' . rand(100, 999),
|
|
'branch_id' => $branchB->id,
|
|
'department_id' => $deptB->id,
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
$hrUser = User::create([
|
|
'name' => 'HR Branch A User',
|
|
'email' => 'hr_a_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'hr',
|
|
'branch_id' => $branchA->id,
|
|
'created_by' => $company->id,
|
|
]);
|
|
$hrUser->givePermissionTo('manage-departments');
|
|
$hrUser->givePermissionTo('manage-any-departments');
|
|
$hrUser->givePermissionTo('manage-employees');
|
|
$hrUser->givePermissionTo('manage-any-employees');
|
|
|
|
// Department index check
|
|
$deptResponse = $this->actingAs($hrUser)->get(route('hr.departments.index'));
|
|
$deptResponse->assertStatus(200);
|
|
$deptResponse->assertSee('Dept Alpha Finance');
|
|
$deptResponse->assertDontSee('Dept Beta Logistics');
|
|
|
|
// Employee index check
|
|
$empResponse = $this->actingAs($hrUser)->get(route('hr.employees.index'));
|
|
$empResponse->assertStatus(200);
|
|
$empResponse->assertSee('Employee Alpha User');
|
|
$empResponse->assertDontSee('Employee Beta User');
|
|
}
|
|
|
|
public function test_branch_scoped_user_only_sees_assigned_branch_payslips()
|
|
{
|
|
$company = User::create([
|
|
'name' => 'Company Admin',
|
|
'email' => 'company_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'company',
|
|
]);
|
|
|
|
$branchA = Branch::create([
|
|
'name' => 'Branch Alpha',
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
$branchB = Branch::create([
|
|
'name' => 'Branch Beta',
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
// Employee A
|
|
$userA = User::create([
|
|
'name' => 'Employee Alpha User',
|
|
'email' => 'alpha_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'employee',
|
|
'created_by' => $company->id,
|
|
]);
|
|
Employee::create([
|
|
'user_id' => $userA->id,
|
|
'employee_id' => 'EMP-A-' . rand(100, 999),
|
|
'branch_id' => $branchA->id,
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
// Employee B
|
|
$userB = User::create([
|
|
'name' => 'Employee Beta User',
|
|
'email' => 'beta_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'employee',
|
|
'created_by' => $company->id,
|
|
]);
|
|
Employee::create([
|
|
'user_id' => $userB->id,
|
|
'employee_id' => 'EMP-B-' . rand(100, 999),
|
|
'branch_id' => $branchB->id,
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
$payrollRun = PayrollRun::create([
|
|
'title' => 'May 1-15 2026',
|
|
'payroll_frequency' => 'monthly',
|
|
'pay_period_start' => '2026-05-01',
|
|
'pay_period_end' => '2026-05-15',
|
|
'pay_date' => '2026-05-15',
|
|
'status' => 'completed',
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
$entryA = PayrollEntry::create([
|
|
'payroll_run_id' => $payrollRun->id,
|
|
'employee_id' => $userA->id,
|
|
'basic_salary' => 10000,
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
$entryB = PayrollEntry::create([
|
|
'payroll_run_id' => $payrollRun->id,
|
|
'employee_id' => $userB->id,
|
|
'basic_salary' => 10000,
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
// Payslips
|
|
$payslipA = Payslip::create([
|
|
'payroll_entry_id' => $entryA->id,
|
|
'employee_id' => $userA->id,
|
|
'payslip_number' => 'PAY-ALPHA-001',
|
|
'pay_period_start' => '2026-05-01',
|
|
'pay_period_end' => '2026-05-15',
|
|
'pay_date' => '2026-05-15',
|
|
'status' => 'generated',
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
$payslipB = Payslip::create([
|
|
'payroll_entry_id' => $entryB->id,
|
|
'employee_id' => $userB->id,
|
|
'payslip_number' => 'PAY-BETA-002',
|
|
'pay_period_start' => '2026-05-01',
|
|
'pay_period_end' => '2026-05-15',
|
|
'pay_date' => '2026-05-15',
|
|
'status' => 'generated',
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
$hrUser = User::create([
|
|
'name' => 'HR Branch A User',
|
|
'email' => 'hr_a_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'hr',
|
|
'branch_id' => $branchA->id,
|
|
'created_by' => $company->id,
|
|
]);
|
|
$hrUser->givePermissionTo('manage-payslips');
|
|
$hrUser->givePermissionTo('manage-any-payslips');
|
|
|
|
$response = $this->actingAs($hrUser)->get(route('hr.payslips.index'));
|
|
$response->assertStatus(200);
|
|
$response->assertSee('PAY-ALPHA-001');
|
|
$response->assertDontSee('PAY-BETA-002');
|
|
}
|
|
|
|
public function test_branch_scoped_user_only_sees_assigned_branch_overtime_requests()
|
|
{
|
|
$company = User::create([
|
|
'name' => 'Company Admin',
|
|
'email' => 'company_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'company',
|
|
]);
|
|
|
|
$branchA = Branch::create([
|
|
'name' => 'Branch Alpha',
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
$branchB = Branch::create([
|
|
'name' => 'Branch Beta',
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
// Employee in Branch A
|
|
$userA = User::create([
|
|
'name' => 'Employee Alpha User',
|
|
'email' => 'alpha_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'employee',
|
|
'created_by' => $company->id,
|
|
]);
|
|
Employee::create([
|
|
'user_id' => $userA->id,
|
|
'employee_id' => 'EMP-A-' . rand(100, 999),
|
|
'branch_id' => $branchA->id,
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
// Employee in Branch B
|
|
$userB = User::create([
|
|
'name' => 'Employee Beta User',
|
|
'email' => 'beta_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'employee',
|
|
'created_by' => $company->id,
|
|
]);
|
|
Employee::create([
|
|
'user_id' => $userB->id,
|
|
'employee_id' => 'EMP-B-' . rand(100, 999),
|
|
'branch_id' => $branchB->id,
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
// Overtime requests
|
|
OvertimeApplication::create([
|
|
'user_id' => $userA->id,
|
|
'date' => '2026-05-10',
|
|
'requested_hours' => 2.5,
|
|
'reason' => 'Alpha Overtime Work',
|
|
'status' => 'pending',
|
|
]);
|
|
|
|
OvertimeApplication::create([
|
|
'user_id' => $userB->id,
|
|
'date' => '2026-05-10',
|
|
'requested_hours' => 3.0,
|
|
'reason' => 'Beta Overtime Work',
|
|
'status' => 'pending',
|
|
]);
|
|
|
|
$hrUser = User::create([
|
|
'name' => 'HR Branch A User',
|
|
'email' => 'hr_a_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'hr',
|
|
'branch_id' => $branchA->id,
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
$response = $this->actingAs($hrUser)->get(route('hr.overtime.index'));
|
|
$response->assertStatus(200);
|
|
$response->assertSee('Alpha Overtime Work');
|
|
$response->assertDontSee('Beta Overtime Work');
|
|
}
|
|
|
|
public function test_branch_scoped_user_only_sees_assigned_branch_thirteenth_month()
|
|
{
|
|
$company = User::create([
|
|
'name' => 'Company Admin',
|
|
'email' => 'company_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'company',
|
|
]);
|
|
|
|
$branchA = Branch::create([
|
|
'name' => 'Branch Alpha',
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
$branchB = Branch::create([
|
|
'name' => 'Branch Beta',
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
// Employee in Branch A
|
|
$userA = User::create([
|
|
'name' => 'Employee Alpha User',
|
|
'email' => 'alpha_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'employee',
|
|
'created_by' => $company->id,
|
|
]);
|
|
Employee::create([
|
|
'user_id' => $userA->id,
|
|
'employee_id' => 'EMP-A-' . rand(100, 999),
|
|
'branch_id' => $branchA->id,
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
// Employee in Branch B
|
|
$userB = User::create([
|
|
'name' => 'Employee Beta User',
|
|
'email' => 'beta_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'employee',
|
|
'created_by' => $company->id,
|
|
]);
|
|
Employee::create([
|
|
'user_id' => $userB->id,
|
|
'employee_id' => 'EMP-B-' . rand(100, 999),
|
|
'branch_id' => $branchB->id,
|
|
'created_by' => $company->id,
|
|
]);
|
|
|
|
$hrUser = User::create([
|
|
'name' => 'HR Branch A User',
|
|
'email' => 'hr_a_' . uniqid() . '@test.com',
|
|
'password' => bcrypt('password'),
|
|
'type' => 'hr',
|
|
'branch_id' => $branchA->id,
|
|
'created_by' => $company->id,
|
|
]);
|
|
$hrUser->givePermissionTo('manage-payroll-runs');
|
|
|
|
$response = $this->actingAs($hrUser)->get(route('hr.thirteenth-month.index'));
|
|
$response->assertStatus(200);
|
|
$response->assertSee('Employee Alpha User');
|
|
$response->assertDontSee('Employee Beta User');
|
|
}
|
|
}
|
|
|
|
|