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

87 lines
2.8 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;
}
// 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 {
$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);
}
}