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']); } }