Files
My-KOPKB/be/app/Services/VisibilityService.php
T
ISMAIL MASSERAN 94ecbe5887 first init
2026-06-08 11:37:14 +08:00

305 lines
12 KiB
PHP

<?php
namespace App\Services;
use Modules\Formation\Entities\Formation;
use Illuminate\Database\Eloquent\Builder;
class VisibilityService
{
/**
* Apply visibility scoping to a query builder based on user permissions
*
* @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 applyVisibilityToQuery(Builder $query, $user, string $unitColumn = 'unit_id', 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
// 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 [];
}
}