can('akses peringkat keseluruhan', Unit::class)) { return $query; } // Get user's unit $userUnit = Unit::find($user->unit_id); if (!$userUnit) { return $query->whereRaw('1 = 0'); } // Government permission: Can view all units under their government // Government users do NOT see units without formations // SEL PERO 91 REJ (process models): ONLY see units without formation - not their gov/formation units if ($user->can('akses peringkat formasi')) { if ($includeUnitsWithoutFormation && $user->hasRole('SEL PERO 91 REJ')) { $unitIds = Unit::whereNull('formation_id') ->whereNull('repair_formation_id') ->pluck('id') ->toArray(); } else { $unitIds = $this->getUnitsUnderGovernment($userUnit); $unitIds = $this->excludeUnitsWithoutFormation($unitIds); } if (!empty($unitIds)) { return $query->whereIn($unitColumn, $unitIds); } return $query->whereRaw('1 = 0'); } // DIV permission: Can view all units under their formation (including repair formations) if ($user->can('akses peringkat divisyen')) { $unitIds = $this->getVisibleUnitIdsForFormationUser($user, $userUnit, $includeUnitsWithoutFormation); if (!empty($unitIds)) { return $query->whereIn($unitColumn, $unitIds); } return $query->whereRaw('1 = 0'); } // Unit permission: Can only view data within their unit return $query->where($unitColumn, $user->unit_id); } /** * Apply visibility scoping for models with indirect unit relationships * * @param bool $includeUnitsWithoutFormation When true (process models), SEL PERO 91 REJ * users can see units without formations. Government users never see units without formations. */ public function applyVisibilityToIndirectQuery(Builder $query, $user, array $unitRelationship, bool $includeUnitsWithoutFormation = false): Builder { // Check if user has akses peringkat keseluruhan permission (super admin) if ($user->can('akses peringkat keseluruhan', Unit::class)) { return $query; } // Get user's unit $userUnit = Unit::find($user->unit_id); if (!$userUnit) { return $query->whereRaw('1 = 0'); } // Government permission: Can view all units under their government // SEL PERO 91 REJ (process models): ONLY see units without formation - not their gov/formation units if ($user->can('akses peringkat formasi')) { if ($includeUnitsWithoutFormation && $user->hasRole('SEL PERO 91 REJ')) { $unitIds = Unit::whereNull('formation_id') ->whereNull('repair_formation_id') ->pluck('id') ->toArray(); } else { $unitIds = $this->getUnitsUnderGovernment($userUnit); $unitIds = $this->excludeUnitsWithoutFormation($unitIds); } if (!empty($unitIds)) { return $query->whereHas($unitRelationship['relationship'], function ($subQuery) use ($unitIds, $unitRelationship) { $subQuery->whereIn($unitRelationship['unitColumn'], $unitIds); }); } return $query->whereRaw('1 = 0'); } // DIV permission: Can view all units under their formation (including repair formations) if ($user->can('akses peringkat divisyen')) { $unitIds = $this->getVisibleUnitIdsForFormationUser($user, $userUnit, $includeUnitsWithoutFormation); if (!empty($unitIds)) { return $query->whereHas($unitRelationship['relationship'], function ($subQuery) use ($unitIds, $unitRelationship) { $subQuery->whereIn($unitRelationship['unitColumn'], $unitIds); }); } return $query->whereRaw('1 = 0'); } // Unit permission: Can only view data within their unit return $query->whereHas($unitRelationship['relationship'], function ($subQuery) use ($user, $unitRelationship) { $subQuery->where($unitRelationship['unitColumn'], $user->unit_id); }); } /** * Get all units under the same government as the user's unit * Handles both direct government relationships and formation-government relationships * Priority: Direct government_id takes precedence over formation government * * @param Unit $userUnit * @return array */ public function getUnitsUnderGovernment(Unit $userUnit): array { $unitIds = collect(); // Case 1: User's unit has direct government relationship (HIGHEST PRIORITY) if ($userUnit->government_id) { $directUnits = Unit::where('government_id', $userUnit->government_id) ->pluck('id'); $unitIds = $unitIds->merge($directUnits); // Also get units in formations under the same direct government $formationUnits = Unit::whereHas('formation', function ($query) use ($userUnit) { $query->where('government_id', $userUnit->government_id); })->pluck('id'); $unitIds = $unitIds->merge($formationUnits); return $unitIds->unique()->toArray(); } // Case 2: User's unit belongs to a formation that has a government (FALLBACK) if ($userUnit->formation_id) { $formation = Formation::find($userUnit->formation_id); if ($formation && $formation->government_id) { // Get all units in formations under the same government $formationUnits = Unit::whereHas('formation', function ($query) use ($formation) { $query->where('government_id', $formation->government_id); })->pluck('id'); $unitIds = $unitIds->merge($formationUnits); // Also get direct government units under the same government $directGovUnits = Unit::where('government_id', $formation->government_id) ->pluck('id'); $unitIds = $unitIds->merge($directGovUnits); } } return $unitIds->unique()->toArray(); } /** * Get the repair formation ID for a unit * Priority: repair_formation_id > formation_id > null * * @param Unit $unit * @return int|null */ public function getRepairFormationId(Unit $unit): ?int { // Priority 1: Check repair_formation_id (custom repair formation) if ($unit->repair_formation_id) { return $unit->repair_formation_id; } // Priority 2: Fall back to regular formation_id if ($unit->formation_id) { return $unit->formation_id; } // Priority 3: No formation (government-level only) return null; } /** * Get visible unit IDs for a user * * @param bool $includeUnitsWithoutFormation When true (process models), SEL PERO 91 REJ * users can see units without formations. Government users never see units without formations. */ public function getVisibleUnitIds($user, bool $includeUnitsWithoutFormation = false): array { // Check if user has akses peringkat keseluruhan permission (super admin) if ($user->can('akses peringkat keseluruhan', Unit::class)) { return Unit::pluck('id')->toArray(); } // Get user's unit $userUnit = Unit::find($user->unit_id); if (!$userUnit) { return []; } // Government permission: Can view all units under their government // SEL PERO 91 REJ (process models): ONLY see units without formation - not their gov/formation units if ($user->can('akses peringkat formasi')) { if ($includeUnitsWithoutFormation && $user->hasRole('SEL PERO 91 REJ')) { return Unit::whereNull('formation_id') ->whereNull('repair_formation_id') ->pluck('id') ->toArray(); } $unitIds = $this->getUnitsUnderGovernment($userUnit); return $this->excludeUnitsWithoutFormation($unitIds); } // DIV permission: Can view all units under their formation (including repair formations) if ($user->can('akses peringkat divisyen')) { return $this->getVisibleUnitIdsForFormationUser($user, $userUnit, $includeUnitsWithoutFormation); } // Unit permission: Can only view data within their unit return [$user->unit_id]; } /** * Exclude units that have no formation (both formation_id and repair_formation_id are null). * Government users do not see these units. */ protected function excludeUnitsWithoutFormation(array $unitIds): array { if (empty($unitIds)) { return []; } return Unit::whereIn('id', $unitIds) ->where(function ($q) { $q->whereNotNull('formation_id') ->orWhereNotNull('repair_formation_id'); }) ->pluck('id') ->toArray(); } /** * Get visible unit IDs for a user with akses peringkat divisyen permission. * * Asset view (includeUnitsWithoutFormation=false): Only units where formation_id matches. * Excludes units that have repair_formation_id but formation_id null (e.g. 10 SKN RAJD uses * 3 DIV for repair only - 3 DIV users see their repairs but NOT their asset data). * * Process view (includeUnitsWithoutFormation=true): Units where repair_formation_id OR * formation_id matches. SEL PERO 91 REJ also sees units without any formation. */ protected function getVisibleUnitIdsForFormationUser($user, Unit $userUnit, bool $includeUnitsWithoutFormation): array { $userRepairFormationId = $this->getRepairFormationId($userUnit); if ($userRepairFormationId) { if (!$includeUnitsWithoutFormation) { // Asset view: Only units that belong to the formation (formation_id matches). // Exclude units that use formation only for repair (formation_id null, repair_formation_id set). return Unit::where('formation_id', $userRepairFormationId) ->pluck('id') ->toArray(); } // SEL PERO 91 REJ: ONLY see units without formation - not their formation's units // (formation units are handled by SEL PERO DIV) if ($user->hasRole('SEL PERO 91 REJ')) { return Unit::whereNull('formation_id') ->whereNull('repair_formation_id') ->pluck('id') ->toArray(); } // Process view: Units in user's formation (repair_formation_id or formation_id) return Unit::where(function ($q) use ($userRepairFormationId) { $q->where('repair_formation_id', $userRepairFormationId) ->orWhere(function ($q2) use ($userRepairFormationId) { $q2->whereNull('repair_formation_id') ->where('formation_id', $userRepairFormationId); }); })->pluck('id')->toArray(); } // User's unit has no formation - check if they have SEL PERO 91 REJ (process models only) if ($includeUnitsWithoutFormation && $user->hasRole('SEL PERO 91 REJ')) { return Unit::whereNull('formation_id') ->whereNull('repair_formation_id') ->pluck('id') ->toArray(); } return []; } }