Files
GSB-Construction/app/Scopes/TenantScope.php

151 lines
5.7 KiB
PHP

<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Support\Facades\Auth;
class TenantScope implements Scope
{
// Static flag to prevent infinite recursion during Auth resolution
protected static bool $resolvingAuth = false;
/**
* Apply the scope to a given Eloquent query builder.
*
* Platform owners (contractor_id = null) bypass all filtering.
* Contractor users see only records within their own company tree.
*/
public function apply(Builder $builder, Model $model): void
{
if (self::$resolvingAuth) {
return;
}
self::$resolvingAuth = true;
try {
if (! Auth::check()) {
return;
}
$user = Auth::user();
if (! $user) {
return;
}
if ($model instanceof \Modules\ProjectManagement\Models\Project && $this->isSiteOperationsUser($user)) {
if ($user->contractor_id) {
$allowedIds = $this->resolveAllowedContractorIds($user->contractor_id);
$builder->where(function ($query) use ($user, $allowedIds, $model) {
$query->whereIn($model->getTable() . '.contractor_id', $allowedIds)
->orWhereHas('contractors', function ($contractorQuery) use ($allowedIds) {
$contractorQuery->whereIn('contractors.id', $allowedIds);
})
->orWhereHas('personnel', function ($personnelQuery) use ($user) {
$personnelQuery->where('users.id', $user->id);
});
});
} else {
$builder->whereHas('personnel', function ($query) use ($user) {
$query->where('users.id', $user->id);
});
}
return;
}
// Project Managers are executive project users, not contractor
// tenants. They need visibility of all projects, including draft
// and completed projects, while action permissions remain enforced
// by gates and controller authorization checks.
if ($model instanceof \Modules\ProjectManagement\Models\Project
&& $user->hasRole('Project Manager')) {
return;
}
// Platform roles always have global visibility. Some existing
// Super Admin/admin accounts were created with a contractor_id;
// the role must take precedence over that stale tenant link.
if ($this->isPlatformUser($user)) {
return;
}
// Platform owners/admins have no contractor_id — full access
if (is_null($user->contractor_id)) {
return;
}
// Gather the authenticated user's own contractor ID plus all direct subcontractors
$allowedIds = $this->resolveAllowedContractorIds($user->contractor_id);
if ($model instanceof \Modules\MasterData\Models\Material) {
$sharesCatalog = \DB::table('contractors')
->where('id', $user->contractor_id)
->value('shares_materials_catalog') ?? true;
if ($sharesCatalog) {
$builder->where(function ($q) use ($model, $allowedIds) {
$q->whereIn($model->getTable() . '.contractor_id', $allowedIds)
->orWhereNull($model->getTable() . '.contractor_id');
});
} else {
$builder->whereIn($model->getTable() . '.contractor_id', $allowedIds);
}
} else {
if ($model instanceof \Modules\ProjectManagement\Models\Project) {
$builder->where(function ($query) use ($model, $allowedIds) {
$query->whereIn($model->getTable() . '.contractor_id', $allowedIds)
->orWhereHas('contractors', function ($contractorQuery) use ($allowedIds) {
$contractorQuery->whereIn('contractors.id', $allowedIds);
});
});
} else {
$builder->whereIn($model->getTable() . '.contractor_id', $allowedIds);
}
}
} finally {
self::$resolvingAuth = false;
}
}
/**
* Build the set of contractor IDs the current user can access.
* This is the user's own contractor plus any direct children (subcontractors).
*
* We deliberately keep this to one level for performance. If you need full
* recursive trees, swap this for a CTE or Spatie-Nested-Set package.
*/
private function resolveAllowedContractorIds(int $contractorId): array
{
// Use DB to avoid loading the Contractor model (prevents circular scope boot)
$childIds = \DB::table('contractors')
->where('parent_id', $contractorId)
->pluck('id')
->toArray();
return array_merge([$contractorId], $childIds);
}
private function isSiteOperationsUser($user): bool
{
return $user->hasAnyRole([
'Contractor Project Manager',
'Site Technical',
'Construction Supervisor',
'Site Operations',
'Site Engineer',
'Site Supervisor',
]);
}
private function isPlatformUser($user): bool
{
return $user->hasAnyRole(['Super Admin', 'admin']);
}
}