feat: 품목기준 필드 관리 기능 개선
- 소프트 삭제된 필드 목록에 표시 (withTrashed) - 삭제된 필드 시각적 구분 (빨간 배경, '삭제됨' 배지) - 필드 복원 기능 추가 (restore API) - 필드 영구 삭제 기능 추가 (forceDelete API) - 체크박스 선택 및 일괄 삭제 기능 추가 - 시스템 필드 삭제 제한 해제 - 커스텀 모달 적용 (showConfirm, showDeleteConfirm)
This commit is contained in:
188
app/Services/SidebarMenuService.php
Normal file
188
app/Services/SidebarMenuService.php
Normal file
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Commons\Menu;
|
||||
use App\Models\Tenants\Department;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class SidebarMenuService
|
||||
{
|
||||
/**
|
||||
* 현재 사용자가 접근 가능한 메뉴 트리 조회
|
||||
*/
|
||||
public function getUserMenuTree(?User $user = null): Collection
|
||||
{
|
||||
$user = $user ?? auth()->user();
|
||||
$tenantId = session('selected_tenant_id', 1);
|
||||
|
||||
// 테넌트의 모든 활성 메뉴 조회
|
||||
$allMenus = Menu::withoutGlobalScopes()
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('is_active', true)
|
||||
->where('hidden', false)
|
||||
->orderBy('sort_order')
|
||||
->get();
|
||||
|
||||
// 슈퍼관리자는 모든 메뉴 표시
|
||||
if ($user && $user->is_super_admin) {
|
||||
return $this->buildMenuTree($allMenus);
|
||||
}
|
||||
|
||||
// 일반 사용자: 부서 권한 기반 메뉴 ID 조회
|
||||
$permittedMenuIds = $this->getPermittedMenuIds($user, $tenantId);
|
||||
|
||||
// 역할 기반 필터링 + 부서 권한 필터링
|
||||
$filteredMenus = $allMenus->filter(function ($menu) use ($user, $permittedMenuIds) {
|
||||
$requiredRole = $menu->getRequiresRole();
|
||||
|
||||
// super_admin 역할 필요시 슈퍼관리자만
|
||||
if ($requiredRole === 'super_admin') {
|
||||
return $user && $user->is_super_admin;
|
||||
}
|
||||
|
||||
// 기타 역할 체크
|
||||
if ($requiredRole && ! ($user && $user->hasRole($requiredRole))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 부서 권한 체크: 허용된 메뉴 ID만 표시
|
||||
return in_array($menu->id, $permittedMenuIds);
|
||||
});
|
||||
|
||||
return $this->buildMenuTree($filteredMenus);
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자가 접근 가능한 메뉴 ID 목록 조회 (부서 권한 기반)
|
||||
*/
|
||||
private function getPermittedMenuIds(?User $user, int $tenantId): array
|
||||
{
|
||||
if (! $user) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 사용자의 부서 ID 조회
|
||||
$departmentIds = DB::table('department_user')
|
||||
->where('user_id', $user->id)
|
||||
->pluck('department_id')
|
||||
->toArray();
|
||||
|
||||
if (empty($departmentIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$now = now();
|
||||
|
||||
// permission_overrides 테이블에서 부서에 ALLOW된 menu:*.view 권한 조회
|
||||
$permittedMenuIds = DB::table('permission_overrides as po')
|
||||
->join('permissions as p', 'p.id', '=', 'po.permission_id')
|
||||
->where('po.model_type', Department::class)
|
||||
->whereIn('po.model_id', $departmentIds)
|
||||
->where('po.tenant_id', $tenantId)
|
||||
->where('po.effect', 1) // ALLOW
|
||||
->where('p.name', 'like', 'menu:%.view')
|
||||
->whereNull('po.deleted_at')
|
||||
->where(function ($query) use ($now) {
|
||||
$query->whereNull('po.effective_from')
|
||||
->orWhere('po.effective_from', '<=', $now);
|
||||
})
|
||||
->where(function ($query) use ($now) {
|
||||
$query->whereNull('po.effective_to')
|
||||
->orWhere('po.effective_to', '>=', $now);
|
||||
})
|
||||
->pluck('p.name')
|
||||
->map(function ($name) {
|
||||
// menu:{id}.view에서 id 추출
|
||||
if (preg_match('/^menu:(\d+)\.view$/', $name, $matches)) {
|
||||
return (int) $matches[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
->filter()
|
||||
->unique()
|
||||
->toArray();
|
||||
|
||||
return $permittedMenuIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 섹션별 메뉴 조회 (main, tools, labs)
|
||||
*/
|
||||
public function getMenusBySection(?User $user = null): array
|
||||
{
|
||||
$menuTree = $this->getUserMenuTree($user);
|
||||
|
||||
return [
|
||||
'main' => $menuTree->filter(fn ($m) => $m->getSection() === 'main')->values(),
|
||||
'tools' => $menuTree->filter(fn ($m) => $m->getSection() === 'tools')->values(),
|
||||
'labs' => $menuTree->filter(fn ($m) => $m->getSection() === 'labs')->values(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 트리 구성
|
||||
*/
|
||||
private function buildMenuTree(Collection $menus, ?int $parentId = null): Collection
|
||||
{
|
||||
return $menus->where('parent_id', $parentId)
|
||||
->map(function ($menu) use ($menus) {
|
||||
$menu->menuChildren = $this->buildMenuTree($menus, $menu->id);
|
||||
|
||||
return $menu;
|
||||
})
|
||||
->values();
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 라우트가 메뉴와 일치하는지 확인
|
||||
*/
|
||||
public function isMenuActive(Menu $menu): bool
|
||||
{
|
||||
$routeName = $menu->getRouteName();
|
||||
|
||||
if ($routeName) {
|
||||
// 라우트 패턴 매칭 (예: pm.* → pm.index, pm.projects.index 등)
|
||||
if (str_ends_with($routeName, '.*')) {
|
||||
$prefix = substr($routeName, 0, -2);
|
||||
|
||||
return request()->routeIs($prefix.'*');
|
||||
}
|
||||
|
||||
return request()->routeIs($routeName);
|
||||
}
|
||||
|
||||
// URL 매칭
|
||||
if ($menu->url) {
|
||||
$currentPath = '/'.ltrim(request()->path(), '/');
|
||||
|
||||
return $currentPath === $menu->url || str_starts_with($currentPath, $menu->url.'/');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 또는 자식 메뉴가 활성 상태인지 확인
|
||||
*/
|
||||
public function isMenuOrChildActive(Menu $menu): bool
|
||||
{
|
||||
if ($this->isMenuActive($menu)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 자식 메뉴 중 활성 상태가 있는지 확인
|
||||
if (isset($menu->menuChildren) && $menu->menuChildren->isNotEmpty()) {
|
||||
foreach ($menu->menuChildren as $child) {
|
||||
if ($this->isMenuOrChildActive($child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user