diff --git a/app/Models/Commons/Menu.php b/app/Models/Commons/Menu.php index 6dad10a0..a7f80ad9 100644 --- a/app/Models/Commons/Menu.php +++ b/app/Models/Commons/Menu.php @@ -17,7 +17,7 @@ class Menu extends Model protected $fillable = [ 'tenant_id', 'parent_id', 'global_menu_id', 'name', 'url', 'is_active', 'sort_order', - 'hidden', 'is_external', 'external_url', 'icon', 'is_customized', + 'hidden', 'is_external', 'external_url', 'icon', 'is_customized', 'options', 'created_by', 'updated_by', 'deleted_by', ]; @@ -33,6 +33,7 @@ class Menu extends Model 'hidden' => 'boolean', 'is_external' => 'boolean', 'is_customized' => 'boolean', + 'options' => 'array', ]; /** @@ -40,7 +41,117 @@ class Menu extends Model */ public static function getSyncFields(): array { - return ['name', 'url', 'icon', 'sort_order', 'is_active', 'hidden', 'is_external', 'external_url']; + return ['name', 'url', 'icon', 'sort_order', 'is_active', 'hidden', 'is_external', 'external_url', 'options']; + } + + // ============================================================ + // Options JSON 헬퍼 메서드 + // ============================================================ + + /** + * options에서 특정 키 값 조회 + */ + public function getOption(string $key, mixed $default = null): mixed + { + return data_get($this->options, $key, $default); + } + + /** + * options에 특정 키 값 설정 + */ + public function setOption(string $key, mixed $value): static + { + $options = $this->options ?? []; + data_set($options, $key, $value); + $this->options = $options; + + return $this; + } + + /** + * 라우트명 조회 (mng, api, react 등에서 사용) + */ + public function getRouteName(): ?string + { + return $this->getOption('route_name'); + } + + /** + * 메뉴 섹션 조회 (main, tools, labs 등) + */ + public function getSection(): string + { + return $this->getOption('section', 'main'); + } + + /** + * 메뉴 타입 조회 (normal, tool, lab 등) + */ + public function getMenuType(): string + { + return $this->getOption('menu_type', 'normal'); + } + + /** + * 필요 역할 조회 + */ + public function getRequiresRole(): ?string + { + return $this->getOption('requires_role'); + } + + /** + * 특정 역할이 필요한지 확인 + */ + public function requiresRole(?string $role = null): bool + { + $requiredRole = $this->getRequiresRole(); + + if ($requiredRole === null) { + return false; + } + + if ($role === null) { + return true; + } + + return $requiredRole === $role; + } + + /** + * Blade 컴포넌트명 조회 + */ + public function getBladeComponent(): ?string + { + return $this->getOption('blade_component'); + } + + /** + * CSS 클래스 조회 + */ + public function getCssClass(): ?string + { + return $this->getOption('css_class'); + } + + /** + * meta 데이터 조회 (앱별 커스텀 데이터) + */ + public function getMeta(?string $key = null, mixed $default = null): mixed + { + if ($key === null) { + return $this->getOption('meta', []); + } + + return $this->getOption("meta.{$key}", $default); + } + + /** + * meta 데이터 설정 + */ + public function setMeta(string $key, mixed $value): static + { + return $this->setOption("meta.{$key}", $value); } public function parent() diff --git a/database/seeders/MngMenuSeeder.php b/database/seeders/MngMenuSeeder.php new file mode 100644 index 00000000..6202ce24 --- /dev/null +++ b/database/seeders/MngMenuSeeder.php @@ -0,0 +1,520 @@ +command->info('MNG 메뉴 시딩 시작...'); + + DB::transaction(function () { + // 기존 메뉴 삭제 (tenant_id=1) + Menu::withoutGlobalScopes() + ->where('tenant_id', $this->tenantId) + ->forceDelete(); + + $this->seedMainMenus(); + $this->seedLabsMenus(); + $this->seedDevToolsMenus(); + }); + + $count = Menu::withoutGlobalScopes()->where('tenant_id', $this->tenantId)->count(); + $this->command->info("MNG 메뉴 시딩 완료! (총 {$count}개)"); + } + + protected function seedMainMenus(): void + { + $sortOrder = 0; + + // ======================================== + // 대시보드 + // ======================================== + $this->createMenu([ + 'name' => '대시보드', + 'url' => '/dashboard', + 'icon' => 'home', + 'sort_order' => $sortOrder++, + 'options' => [ + 'route_name' => 'dashboard', + 'section' => 'main', + ], + ]); + + // ======================================== + // 프로젝트 관리 그룹 + // ======================================== + $pmGroup = $this->createMenu([ + 'name' => '프로젝트 관리', + 'url' => '#', + 'icon' => 'folder', + 'sort_order' => $sortOrder++, + 'options' => [ + 'section' => 'main', + 'meta' => ['group_id' => 'pm-group'], + ], + ]); + + $pmSubOrder = 0; + $this->createMenu([ + 'parent_id' => $pmGroup->id, + 'name' => '프로젝트 대시보드', + 'url' => '/pm', + 'icon' => 'chart-bar', + 'sort_order' => $pmSubOrder++, + 'options' => ['route_name' => 'pm.index', 'section' => 'main'], + ]); + $this->createMenu([ + 'parent_id' => $pmGroup->id, + 'name' => '프로젝트', + 'url' => '/pm/projects', + 'icon' => 'folder', + 'sort_order' => $pmSubOrder++, + 'options' => ['route_name' => 'pm.projects.index', 'section' => 'main'], + ]); + $this->createMenu([ + 'parent_id' => $pmGroup->id, + 'name' => '일일 스크럼', + 'url' => '/daily-logs', + 'icon' => 'calendar', + 'sort_order' => $pmSubOrder++, + 'options' => ['route_name' => 'daily-logs.index', 'section' => 'main'], + ]); + + // ======================================== + // 시스템 관리 그룹 + // ======================================== + $systemGroup = $this->createMenu([ + 'name' => '시스템 관리', + 'url' => '#', + 'icon' => 'cog', + 'sort_order' => $sortOrder++, + 'options' => [ + 'section' => 'main', + 'meta' => ['group_id' => 'system-group'], + ], + ]); + + $systemSubOrder = 0; + $this->createMenu([ + 'parent_id' => $systemGroup->id, + 'name' => '테넌트 관리', + 'url' => '/tenants', + 'icon' => 'building', + 'sort_order' => $systemSubOrder++, + 'options' => ['route_name' => 'tenants.index', 'section' => 'main'], + ]); + $this->createMenu([ + 'parent_id' => $systemGroup->id, + 'name' => '사용자 관리', + 'url' => '/users', + 'icon' => 'users', + 'sort_order' => $systemSubOrder++, + 'options' => ['route_name' => 'users.index', 'section' => 'main'], + ]); + $this->createMenu([ + 'parent_id' => $systemGroup->id, + 'name' => '부서 관리', + 'url' => '/departments', + 'icon' => 'building', + 'sort_order' => $systemSubOrder++, + 'options' => ['route_name' => 'departments.index', 'section' => 'main'], + ]); + $this->createMenu([ + 'parent_id' => $systemGroup->id, + 'name' => '메뉴 관리', + 'url' => '/menus', + 'icon' => 'menu', + 'sort_order' => $systemSubOrder++, + 'options' => ['route_name' => 'menus.index', 'section' => 'main'], + ]); + + // ======================================== + // 권한 관리 그룹 + // ======================================== + $permGroup = $this->createMenu([ + 'name' => '권한 관리', + 'url' => '#', + 'icon' => 'shield', + 'sort_order' => $sortOrder++, + 'options' => [ + 'section' => 'main', + 'meta' => ['group_id' => 'permission-group'], + ], + ]); + + $permSubOrder = 0; + $this->createMenu([ + 'parent_id' => $permGroup->id, + 'name' => '역할 관리', + 'url' => '/roles', + 'icon' => 'shield-check', + 'sort_order' => $permSubOrder++, + 'options' => ['route_name' => 'roles.index', 'section' => 'main'], + ]); + $this->createMenu([ + 'parent_id' => $permGroup->id, + 'name' => '권한 관리', + 'url' => '/permissions', + 'icon' => 'lock', + 'sort_order' => $permSubOrder++, + 'options' => ['route_name' => 'permissions.index', 'section' => 'main'], + ]); + $this->createMenu([ + 'parent_id' => $permGroup->id, + 'name' => '역할 권한 관리', + 'url' => '/role-permissions', + 'icon' => 'shield-check', + 'sort_order' => $permSubOrder++, + 'options' => ['route_name' => 'role-permissions.index', 'section' => 'main'], + ]); + $this->createMenu([ + 'parent_id' => $permGroup->id, + 'name' => '부서 권한 관리', + 'url' => '/department-permissions', + 'icon' => 'building', + 'sort_order' => $permSubOrder++, + 'options' => ['route_name' => 'department-permissions.index', 'section' => 'main'], + ]); + $this->createMenu([ + 'parent_id' => $permGroup->id, + 'name' => '개인 권한 관리', + 'url' => '/user-permissions', + 'icon' => 'user', + 'sort_order' => $permSubOrder++, + 'options' => ['route_name' => 'user-permissions.index', 'section' => 'main'], + ]); + $this->createMenu([ + 'parent_id' => $permGroup->id, + 'name' => '권한 분석', + 'url' => '/permission-analyze', + 'icon' => 'chart-bar', + 'sort_order' => $permSubOrder++, + 'options' => ['route_name' => 'permission-analyze.index', 'section' => 'main'], + ]); + + // ======================================== + // 생산 관리 그룹 + // ======================================== + $productionGroup = $this->createMenu([ + 'name' => '생산 관리', + 'url' => '#', + 'icon' => 'cube', + 'sort_order' => $sortOrder++, + 'options' => [ + 'section' => 'main', + 'meta' => ['group_id' => 'production-group'], + ], + ]); + + $prodSubOrder = 0; + $this->createMenu([ + 'parent_id' => $productionGroup->id, + 'name' => '품목기준 필드 관리', + 'url' => '/item-fields', + 'icon' => 'list', + 'sort_order' => $prodSubOrder++, + 'options' => ['route_name' => 'item-fields.index', 'section' => 'main'], + ]); + $this->createMenu([ + 'parent_id' => $productionGroup->id, + 'name' => '견적수식 관리', + 'url' => '/quote-formulas', + 'icon' => 'calculator', + 'sort_order' => $prodSubOrder++, + 'options' => ['route_name' => 'quote-formulas.index', 'section' => 'main'], + ]); + $this->createMenu([ + 'parent_id' => $productionGroup->id, + 'name' => '제품 관리', + 'url' => '/products', + 'icon' => 'cube', + 'sort_order' => $prodSubOrder++, + 'hidden' => true, + 'options' => ['route_name' => 'products.index', 'section' => 'main', 'meta' => ['status' => 'preparing']], + ]); + $this->createMenu([ + 'parent_id' => $productionGroup->id, + 'name' => '자재 관리', + 'url' => '/materials', + 'icon' => 'archive', + 'sort_order' => $prodSubOrder++, + 'hidden' => true, + 'options' => ['route_name' => 'materials.index', 'section' => 'main', 'meta' => ['status' => 'preparing']], + ]); + $this->createMenu([ + 'parent_id' => $productionGroup->id, + 'name' => 'BOM 관리', + 'url' => '/bom', + 'icon' => 'clipboard-list', + 'sort_order' => $prodSubOrder++, + 'hidden' => true, + 'options' => ['route_name' => 'bom.index', 'section' => 'main', 'meta' => ['status' => 'preparing']], + ]); + $this->createMenu([ + 'parent_id' => $productionGroup->id, + 'name' => '카테고리 관리', + 'url' => '/categories', + 'icon' => 'tag', + 'sort_order' => $prodSubOrder++, + 'hidden' => true, + 'options' => ['route_name' => 'categories.index', 'section' => 'main', 'meta' => ['status' => 'preparing']], + ]); + + // ======================================== + // 콘텐츠 관리 그룹 + // ======================================== + $contentGroup = $this->createMenu([ + 'name' => '콘텐츠 관리', + 'url' => '#', + 'icon' => 'document-text', + 'sort_order' => $sortOrder++, + 'options' => [ + 'section' => 'main', + 'meta' => ['group_id' => 'content-group'], + ], + ]); + + $this->createMenu([ + 'parent_id' => $contentGroup->id, + 'name' => '게시판 관리', + 'url' => '/boards', + 'icon' => 'clipboard', + 'sort_order' => 0, + 'options' => ['route_name' => 'boards.index', 'section' => 'main'], + ]); + + // ======================================== + // 시스템 그룹 + // ======================================== + $settingsGroup = $this->createMenu([ + 'name' => '시스템', + 'url' => '#', + 'icon' => 'cog', + 'sort_order' => $sortOrder++, + 'options' => [ + 'section' => 'main', + 'meta' => ['group_id' => 'system-settings-group'], + ], + ]); + + $settingsSubOrder = 0; + $this->createMenu([ + 'parent_id' => $settingsGroup->id, + 'name' => '시스템 설정', + 'url' => '/settings', + 'icon' => 'cog', + 'sort_order' => $settingsSubOrder++, + 'hidden' => true, + 'options' => ['route_name' => 'settings.index', 'section' => 'main', 'meta' => ['status' => 'preparing']], + ]); + $this->createMenu([ + 'parent_id' => $settingsGroup->id, + 'name' => '감사 로그', + 'url' => '/audit-logs', + 'icon' => 'document-text', + 'sort_order' => $settingsSubOrder++, + 'hidden' => true, + 'options' => ['route_name' => 'audit-logs.index', 'section' => 'main', 'meta' => ['status' => 'preparing']], + ]); + $this->createMenu([ + 'parent_id' => $settingsGroup->id, + 'name' => '삭제된 데이터 백업', + 'url' => '/archived-records', + 'icon' => 'database', + 'sort_order' => $settingsSubOrder++, + 'options' => ['route_name' => 'archived-records.index', 'section' => 'main'], + ]); + } + + protected function seedLabsMenus(): void + { + // ======================================== + // R&D Labs 그룹 (S/A/M 탭 구조) + // ======================================== + $labsGroup = $this->createMenu([ + 'name' => 'R&D Labs', + 'url' => '#', + 'icon' => 'beaker', + 'sort_order' => 100, + 'options' => [ + 'section' => 'labs', + 'menu_type' => 'lab', + 'meta' => [ + 'group_id' => 'lab-group', + 'tabs' => ['S', 'A', 'M'], + ], + ], + ]); + + // S 탭 메뉴들 (Strategy) + $sMenus = [ + ['name' => '세무 전략', 'url' => '/lab/strategy/tax', 'route' => 'lab.strategy.tax'], + ['name' => '노무 전략', 'url' => '/lab/strategy/labor', 'route' => 'lab.strategy.labor'], + ['name' => '채권추심 전략', 'url' => '/lab/strategy/debt', 'route' => 'lab.strategy.debt'], + ['name' => '스테이블코인 보고서', 'url' => '/lab/strategy/stablecoin', 'route' => 'lab.strategy.stablecoin'], + ['name' => 'MRP 해외사례', 'url' => '/lab/strategy/mrp-overseas', 'route' => 'lab.strategy.mrp-overseas'], + ['name' => '상담용 챗봇 전략', 'url' => '/lab/strategy/chatbot', 'route' => 'lab.strategy.chatbot'], + ['name' => 'KoDATA vs NICE API', 'url' => '/lab/strategy/kodata-vs-nice', 'route' => 'lab.strategy.kodata-vs-nice'], + ['name' => '바로빌 vs 팝빌 API', 'url' => '/lab/strategy/barobill-vs-popbill', 'route' => 'lab.strategy.barobill-vs-popbill'], + ['name' => '사내 지식 검색 시스템', 'url' => '/lab/strategy/knowledge-search', 'route' => 'lab.strategy.knowledge-search'], + ['name' => '챗봇 솔루션 비교 분석', 'url' => '/lab/strategy/chatbot-compare', 'route' => 'lab.strategy.chatbot-compare'], + ['name' => 'RAG 스타트업 현황', 'url' => '/lab/strategy/rag-startups', 'route' => 'lab.strategy.rag-startups'], + ['name' => '더존비즈온 분석', 'url' => '/lab/strategy/douzone', 'route' => 'lab.strategy.douzone'], + ['name' => 'Confluence vs Notion', 'url' => '/lab/strategy/confluence-vs-notion', 'route' => 'lab.strategy.confluence-vs-notion'], + ['name' => '차세대 QA 솔루션', 'url' => '/lab/strategy/qa-solution', 'route' => 'lab.strategy.qa-solution'], + ['name' => 'SAM 영업전략', 'url' => '/lab/strategy/sales-strategy', 'route' => 'lab.strategy.sales-strategy'], + ]; + + foreach ($sMenus as $i => $menu) { + $this->createMenu([ + 'parent_id' => $labsGroup->id, + 'name' => $menu['name'], + 'url' => $menu['url'], + 'icon' => 'document', + 'sort_order' => $i, + 'options' => [ + 'route_name' => $menu['route'], + 'section' => 'labs', + 'menu_type' => 'lab', + 'meta' => ['tab' => 'S'], + ], + ]); + } + + // A 탭 메뉴들 (AI/Automation) + $aMenus = [ + ['name' => '사업자등록증 OCR', 'url' => '/lab/ai/business-ocr', 'route' => 'lab.ai.business-ocr'], + ['name' => '웹 녹음 AI 요약', 'url' => '/lab/ai/web-recording', 'route' => 'lab.ai.web-recording'], + ['name' => '회의록 AI 요약', 'url' => '/lab/ai/meeting-summary', 'route' => 'lab.ai.meeting-summary'], + ['name' => '업무협의록 AI 요약', 'url' => '/lab/ai/work-memo-summary', 'route' => 'lab.ai.work-memo-summary'], + ['name' => '운영자용 챗봇', 'url' => '/lab/ai/operator-chatbot', 'route' => 'lab.ai.operator-chatbot'], + ['name' => 'Vertex RAG 챗봇', 'url' => '/lab/ai/vertex-rag', 'route' => 'lab.ai.vertex-rag'], + ['name' => '테넌트 지식 업로드', 'url' => '/lab/ai/tenant-knowledge', 'route' => 'lab.ai.tenant-knowledge'], + ['name' => '테넌트 챗봇', 'url' => '/lab/ai/tenant-chatbot', 'route' => 'lab.ai.tenant-chatbot'], + ['name' => 'SAM AI 메뉴 이동', 'url' => '/lab/ai/sam-ai-menu', 'route' => 'lab.ai.sam-ai-menu'], + ['name' => 'SAM AI 알람음 제작', 'url' => '/lab/ai/sam-ai-alarm', 'route' => 'lab.ai.sam-ai-alarm'], + ['name' => 'GPS 출퇴근 관리', 'url' => '/lab/ai/gps-attendance', 'route' => 'lab.ai.gps-attendance'], + ['name' => '기업개황 조회', 'url' => '/lab/ai/company-overview', 'route' => 'lab.ai.company-overview'], + ]; + + foreach ($aMenus as $i => $menu) { + $this->createMenu([ + 'parent_id' => $labsGroup->id, + 'name' => $menu['name'], + 'url' => $menu['url'], + 'icon' => 'chip', + 'sort_order' => 100 + $i, + 'options' => [ + 'route_name' => $menu['route'], + 'section' => 'labs', + 'menu_type' => 'lab', + 'meta' => ['tab' => 'A'], + ], + ]); + } + + // M 탭 메뉴들 (Management) + $mMenus = [ + ['name' => '바로빌 테넌트 관리', 'url' => '/lab/management/barobill-tenant', 'route' => 'lab.management.barobill-tenant'], + ['name' => '전자세금계산서 전략', 'url' => '/lab/management/tax-invoice-strategy', 'route' => 'lab.management.tax-invoice-strategy'], + ['name' => '전자세금계산서', 'url' => '/lab/management/tax-invoice', 'route' => 'lab.management.tax-invoice'], + ['name' => '사업자등록번호 진위 확인', 'url' => '/lab/management/business-verify', 'route' => 'lab.management.business-verify'], + ['name' => '영업관리 & 매니저 미팅관리', 'url' => '/lab/management/sales-meeting', 'route' => 'lab.management.sales-meeting'], + ['name' => '카드 세무항목 매칭 전략', 'url' => '/lab/management/card-tax-matching', 'route' => 'lab.management.card-tax-matching'], + ['name' => '한국 카드사 API 보고서', 'url' => '/lab/management/card-api-report', 'route' => 'lab.management.card-api-report'], + ['name' => '카드 사용내역 수집 후 매칭', 'url' => '/lab/management/card-usage-matching', 'route' => 'lab.management.card-usage-matching'], + ['name' => '계좌입출금 내역 조회 API', 'url' => '/lab/management/account-api', 'route' => 'lab.management.account-api'], + ['name' => '영업관리 시나리오', 'url' => '/lab/management/sales-scenario', 'route' => 'lab.management.sales-scenario'], + ['name' => '매니저 시나리오', 'url' => '/lab/management/manager-scenario', 'route' => 'lab.management.manager-scenario'], + ]; + + foreach ($mMenus as $i => $menu) { + $this->createMenu([ + 'parent_id' => $labsGroup->id, + 'name' => $menu['name'], + 'url' => $menu['url'], + 'icon' => 'briefcase', + 'sort_order' => 200 + $i, + 'options' => [ + 'route_name' => $menu['route'], + 'section' => 'labs', + 'menu_type' => 'lab', + 'meta' => ['tab' => 'M'], + ], + ]); + } + } + + protected function seedDevToolsMenus(): void + { + // ======================================== + // 개발 도구 그룹 (하단 고정) + // ======================================== + $devToolsGroup = $this->createMenu([ + 'name' => '개발 도구', + 'url' => '#', + 'icon' => 'code', + 'sort_order' => 900, + 'options' => [ + 'section' => 'tools', + 'menu_type' => 'tool', + 'meta' => ['group_id' => 'dev-tools-group', 'position' => 'bottom'], + ], + ]); + + $devSubOrder = 0; + $this->createMenu([ + 'parent_id' => $devToolsGroup->id, + 'name' => 'API 플로우 테스터', + 'url' => '/dev-tools/flow-tester', + 'icon' => 'terminal', + 'sort_order' => $devSubOrder++, + 'options' => [ + 'route_name' => 'dev-tools.flow-tester.index', + 'section' => 'tools', + 'menu_type' => 'tool', + ], + ]); + $this->createMenu([ + 'parent_id' => $devToolsGroup->id, + 'name' => 'API 요청 로그', + 'url' => '/dev-tools/api-logs', + 'icon' => 'document-text', + 'sort_order' => $devSubOrder++, + 'options' => [ + 'route_name' => 'dev-tools.api-logs.index', + 'section' => 'tools', + 'menu_type' => 'tool', + ], + ]); + } + + protected function createMenu(array $data): Menu + { + $defaults = [ + 'tenant_id' => $this->tenantId, + 'is_active' => true, + 'hidden' => false, + 'is_external' => false, + 'is_customized' => false, + ]; + + return Menu::create(array_merge($defaults, $data)); + } +}