diff --git a/app/Http/Controllers/Api/Admin/DocumentTemplateApiController.php b/app/Http/Controllers/Api/Admin/DocumentTemplateApiController.php new file mode 100644 index 00000000..dca3a525 --- /dev/null +++ b/app/Http/Controllers/Api/Admin/DocumentTemplateApiController.php @@ -0,0 +1,320 @@ +withCount(['sections', 'columns']); + + // 검색 + if ($search = $request->input('search')) { + $query->where(function ($q) use ($search) { + $q->where('name', 'like', "%{$search}%") + ->orWhere('title', 'like', "%{$search}%") + ->orWhere('category', 'like', "%{$search}%"); + }); + } + + // 카테고리 필터 + if ($category = $request->input('category')) { + $query->where('category', $category); + } + + // 활성 상태 필터 + if ($request->has('is_active') && $request->input('is_active') !== '') { + $query->where('is_active', $request->boolean('is_active')); + } + + $templates = $query->orderBy('updated_at', 'desc') + ->paginate($request->input('per_page', 10)); + + return view('document-templates.partials.table', compact('templates')); + } + + /** + * 단일 조회 + */ + public function show(int $id): JsonResponse + { + $template = DocumentTemplate::with([ + 'approvalLines', + 'basicFields', + 'sections.items', + 'columns', + ])->findOrFail($id); + + return response()->json([ + 'success' => true, + 'data' => $template, + ]); + } + + /** + * 생성 + */ + public function store(Request $request): JsonResponse + { + $validated = $request->validate([ + 'name' => 'required|string|max:100', + 'category' => 'nullable|string|max:50', + 'title' => 'nullable|string|max:200', + 'company_name' => 'nullable|string|max:100', + 'company_address' => 'nullable|string|max:255', + 'company_contact' => 'nullable|string|max:100', + 'footer_remark_label' => 'nullable|string|max:50', + 'footer_judgement_label' => 'nullable|string|max:50', + 'footer_judgement_options' => 'nullable|array', + 'is_active' => 'boolean', + // 관계 데이터 + 'approval_lines' => 'nullable|array', + 'basic_fields' => 'nullable|array', + 'sections' => 'nullable|array', + 'columns' => 'nullable|array', + ]); + + try { + DB::beginTransaction(); + + $template = DocumentTemplate::create([ + 'tenant_id' => session('selected_tenant_id'), + 'name' => $validated['name'], + 'category' => $validated['category'] ?? null, + 'title' => $validated['title'] ?? null, + 'company_name' => $validated['company_name'] ?? '경동기업', + 'company_address' => $validated['company_address'] ?? null, + 'company_contact' => $validated['company_contact'] ?? null, + 'footer_remark_label' => $validated['footer_remark_label'] ?? '부적합 내용', + 'footer_judgement_label' => $validated['footer_judgement_label'] ?? '종합판정', + 'footer_judgement_options' => $validated['footer_judgement_options'] ?? ['적합', '부적합'], + 'is_active' => $validated['is_active'] ?? true, + ]); + + // 관계 데이터 저장 + $this->saveRelations($template, $validated); + + DB::commit(); + + return response()->json([ + 'success' => true, + 'message' => '문서양식이 생성되었습니다.', + 'data' => $template->load(['approvalLines', 'basicFields', 'sections.items', 'columns']), + ]); + } catch (\Exception $e) { + DB::rollBack(); + + return response()->json([ + 'success' => false, + 'message' => '생성 중 오류가 발생했습니다: '.$e->getMessage(), + ], 500); + } + } + + /** + * 수정 + */ + public function update(Request $request, int $id): JsonResponse + { + $template = DocumentTemplate::findOrFail($id); + + $validated = $request->validate([ + 'name' => 'required|string|max:100', + 'category' => 'nullable|string|max:50', + 'title' => 'nullable|string|max:200', + 'company_name' => 'nullable|string|max:100', + 'company_address' => 'nullable|string|max:255', + 'company_contact' => 'nullable|string|max:100', + 'footer_remark_label' => 'nullable|string|max:50', + 'footer_judgement_label' => 'nullable|string|max:50', + 'footer_judgement_options' => 'nullable|array', + 'is_active' => 'boolean', + // 관계 데이터 + 'approval_lines' => 'nullable|array', + 'basic_fields' => 'nullable|array', + 'sections' => 'nullable|array', + 'columns' => 'nullable|array', + ]); + + try { + DB::beginTransaction(); + + $template->update([ + 'name' => $validated['name'], + 'category' => $validated['category'] ?? null, + 'title' => $validated['title'] ?? null, + 'company_name' => $validated['company_name'] ?? null, + 'company_address' => $validated['company_address'] ?? null, + 'company_contact' => $validated['company_contact'] ?? null, + 'footer_remark_label' => $validated['footer_remark_label'] ?? '부적합 내용', + 'footer_judgement_label' => $validated['footer_judgement_label'] ?? '종합판정', + 'footer_judgement_options' => $validated['footer_judgement_options'] ?? ['적합', '부적합'], + 'is_active' => $validated['is_active'] ?? true, + ]); + + // 관계 데이터 저장 (기존 데이터 삭제 후 재생성) + $this->saveRelations($template, $validated, true); + + DB::commit(); + + return response()->json([ + 'success' => true, + 'message' => '문서양식이 수정되었습니다.', + 'data' => $template->fresh(['approvalLines', 'basicFields', 'sections.items', 'columns']), + ]); + } catch (\Exception $e) { + DB::rollBack(); + + return response()->json([ + 'success' => false, + 'message' => '수정 중 오류가 발생했습니다: '.$e->getMessage(), + ], 500); + } + } + + /** + * 삭제 + */ + public function destroy(int $id): JsonResponse + { + $template = DocumentTemplate::findOrFail($id); + $template->delete(); + + return response()->json([ + 'success' => true, + 'message' => '문서양식이 삭제되었습니다.', + ]); + } + + /** + * 활성 상태 토글 + */ + public function toggleActive(int $id): JsonResponse + { + $template = DocumentTemplate::findOrFail($id); + $template->update(['is_active' => ! $template->is_active]); + + return response()->json([ + 'success' => true, + 'message' => $template->is_active ? '활성화되었습니다.' : '비활성화되었습니다.', + 'is_active' => $template->is_active, + ]); + } + + /** + * 이미지 업로드 + */ + public function uploadImage(Request $request): JsonResponse + { + $request->validate([ + 'image' => 'required|image|max:5120', // 5MB + ]); + + $path = $request->file('image')->store('document-templates', 'public'); + + return response()->json([ + 'success' => true, + 'path' => $path, + 'url' => asset('storage/'.$path), + ]); + } + + /** + * 관계 데이터 저장 + */ + private function saveRelations(DocumentTemplate $template, array $data, bool $deleteExisting = false): void + { + // 기존 데이터 삭제 (수정 시) + if ($deleteExisting) { + $template->approvalLines()->delete(); + $template->basicFields()->delete(); + // sections는 cascade로 items도 함께 삭제됨 + $template->sections()->delete(); + $template->columns()->delete(); + } + + // 결재라인 + if (! empty($data['approval_lines'])) { + foreach ($data['approval_lines'] as $index => $line) { + DocumentTemplateApprovalLine::create([ + 'template_id' => $template->id, + 'name' => $line['name'] ?? '', + 'dept' => $line['dept'] ?? '', + 'role' => $line['role'] ?? '', + 'sort_order' => $index, + ]); + } + } + + // 기본 필드 + if (! empty($data['basic_fields'])) { + foreach ($data['basic_fields'] as $index => $field) { + DocumentTemplateBasicField::create([ + 'template_id' => $template->id, + 'label' => $field['label'] ?? '', + 'field_type' => $field['field_type'] ?? 'text', + 'default_value' => $field['default_value'] ?? '', + 'sort_order' => $index, + ]); + } + } + + // 섹션 및 항목 + if (! empty($data['sections'])) { + foreach ($data['sections'] as $sIndex => $section) { + $newSection = DocumentTemplateSection::create([ + 'template_id' => $template->id, + 'title' => $section['title'] ?? '', + 'image_path' => $section['image_path'] ?? null, + 'sort_order' => $sIndex, + ]); + + if (! empty($section['items'])) { + foreach ($section['items'] as $iIndex => $item) { + DocumentTemplateSectionItem::create([ + 'section_id' => $newSection->id, + 'category' => $item['category'] ?? '', + 'item' => $item['item'] ?? '', + 'standard' => $item['standard'] ?? '', + 'method' => $item['method'] ?? '', + 'frequency' => $item['frequency'] ?? '', + 'regulation' => $item['regulation'] ?? '', + 'sort_order' => $iIndex, + ]); + } + } + } + } + + // 컬럼 + if (! empty($data['columns'])) { + foreach ($data['columns'] as $index => $column) { + DocumentTemplateColumn::create([ + 'template_id' => $template->id, + 'label' => $column['label'] ?? '', + 'width' => $column['width'] ?? '100px', + 'column_type' => $column['column_type'] ?? 'text', + 'group_name' => $column['group_name'] ?? null, + 'sub_labels' => $column['sub_labels'] ?? null, + 'sort_order' => $index, + ]); + } + } + } +} \ No newline at end of file diff --git a/app/Http/Controllers/CommonCodeController.php b/app/Http/Controllers/CommonCodeController.php index 094bfa86..236f6e04 100644 --- a/app/Http/Controllers/CommonCodeController.php +++ b/app/Http/Controllers/CommonCodeController.php @@ -34,6 +34,7 @@ class CommonCodeController extends Controller 'bad_debt_progress' => '대손진행', 'height_construction_cost' => '높이시공비', 'width_construction_cost' => '폭시공비', + 'document_type' => '문서분류', ]; /** @@ -318,6 +319,100 @@ public function copy(Request $request, int $id): RedirectResponse|JsonResponse ->with('success', '글로벌 코드가 테넌트용으로 복사되었습니다.'); } + /** + * 글로벌 코드를 테넌트용으로 일괄 복사 + */ + public function bulkCopy(Request $request): RedirectResponse|JsonResponse + { + $tenantId = session('selected_tenant_id'); + + if (! $tenantId) { + if ($request->ajax()) { + return response()->json(['error' => '테넌트를 먼저 선택해주세요.'], 400); + } + return redirect()->back()->with('error', '테넌트를 먼저 선택해주세요.'); + } + + // JSON 문자열로 받은 경우 처리 + $idsJson = $request->input('ids_json'); + if ($idsJson) { + $ids = json_decode($idsJson, true); + if (! is_array($ids) || empty($ids)) { + if ($request->ajax()) { + return response()->json(['error' => '복사할 코드를 선택해주세요.'], 400); + } + return redirect()->back()->with('error', '복사할 코드를 선택해주세요.'); + } + } else { + $validated = $request->validate([ + 'ids' => 'required|array|min:1', + 'ids.*' => 'integer', + ]); + $ids = $validated['ids']; + } + $codeGroup = null; + $copiedCount = 0; + $skippedCount = 0; + + DB::beginTransaction(); + try { + foreach ($ids as $id) { + $globalCode = CommonCode::whereNull('tenant_id')->find($id); + if (! $globalCode) { + continue; + } + + $codeGroup = $globalCode->code_group; + + // 이미 복사된 코드가 있는지 확인 + $exists = CommonCode::query() + ->where('tenant_id', $tenantId) + ->where('code_group', $globalCode->code_group) + ->where('code', $globalCode->code) + ->exists(); + + if ($exists) { + $skippedCount++; + continue; + } + + // 복사 + CommonCode::create([ + 'tenant_id' => $tenantId, + 'code_group' => $globalCode->code_group, + 'code' => $globalCode->code, + 'name' => $globalCode->name, + 'sort_order' => $globalCode->sort_order, + 'attributes' => $globalCode->attributes, + 'is_active' => true, + ]); + + $copiedCount++; + } + + DB::commit(); + } catch (\Exception $e) { + DB::rollBack(); + if ($request->ajax()) { + return response()->json(['error' => '복사 중 오류가 발생했습니다.'], 500); + } + return redirect()->back()->with('error', '복사 중 오류가 발생했습니다.'); + } + + $message = "{$copiedCount}개 코드가 복사되었습니다."; + if ($skippedCount > 0) { + $message .= " ({$skippedCount}개는 이미 존재하여 건너뜀)"; + } + + if ($request->ajax()) { + return response()->json(['success' => true, 'message' => $message, 'copied' => $copiedCount, 'skipped' => $skippedCount]); + } + + return redirect() + ->route('common-codes.index', ['group' => $codeGroup ?? 'item_type']) + ->with('success', $message); + } + /** * 코드 삭제 (테넌트 코드만) */ diff --git a/app/Http/Controllers/DocumentTemplateController.php b/app/Http/Controllers/DocumentTemplateController.php new file mode 100644 index 00000000..22d18f54 --- /dev/null +++ b/app/Http/Controllers/DocumentTemplateController.php @@ -0,0 +1,133 @@ + $this->getDocumentTypes(), + ]); + } + + /** + * 문서양식 생성 페이지 + */ + public function create(): View + { + return view('document-templates.edit', [ + 'template' => null, + 'templateData' => null, + 'isCreate' => true, + 'documentTypes' => $this->getDocumentTypes(), + ]); + } + + /** + * 문서양식 수정 페이지 + */ + public function edit(int $id): View + { + $template = DocumentTemplate::with([ + 'approvalLines', + 'basicFields', + 'sections.items', + 'columns', + ])->findOrFail($id); + + // JavaScript용 데이터 변환 + $templateData = $this->prepareTemplateData($template); + + return view('document-templates.edit', [ + 'template' => $template, + 'templateData' => $templateData, + 'isCreate' => false, + 'documentTypes' => $this->getDocumentTypes(), + ]); + } + + /** + * 문서분류 목록 조회 (글로벌 + 테넌트) + */ + private function getDocumentTypes(): array + { + $tenantId = session('selected_tenant_id'); + + return CommonCode::query() + ->where(function ($query) use ($tenantId) { + $query->whereNull('tenant_id'); + if ($tenantId) { + $query->orWhere('tenant_id', $tenantId); + } + }) + ->where('code_group', 'document_type') + ->where('is_active', true) + ->orderBy('sort_order') + ->pluck('name', 'code') + ->toArray(); + } + + /** + * JavaScript용 템플릿 데이터 준비 + */ + private function prepareTemplateData(DocumentTemplate $template): array + { + return [ + 'name' => $template->name, + 'category' => $template->category, + 'title' => $template->title, + 'company_name' => $template->company_name, + 'company_address' => $template->company_address, + 'company_contact' => $template->company_contact, + 'footer_remark_label' => $template->footer_remark_label, + 'footer_judgement_label' => $template->footer_judgement_label, + 'footer_judgement_options' => $template->footer_judgement_options, + 'is_active' => $template->is_active, + 'approval_lines' => $template->approvalLines->map(function ($l) { + return [ + 'id' => $l->id, + 'name' => $l->name, + 'dept' => $l->dept, + 'role' => $l->role, + ]; + })->toArray(), + 'sections' => $template->sections->map(function ($s) { + return [ + 'id' => $s->id, + 'title' => $s->title, + 'image_path' => $s->image_path, + 'items' => $s->items->map(function ($i) { + return [ + 'id' => $i->id, + 'category' => $i->category, + 'item' => $i->item, + 'standard' => $i->standard, + 'method' => $i->method, + 'frequency' => $i->frequency, + 'regulation' => $i->regulation, + ]; + })->toArray(), + ]; + })->toArray(), + 'columns' => $template->columns->map(function ($c) { + return [ + 'id' => $c->id, + 'label' => $c->label, + 'width' => $c->width, + 'column_type' => $c->column_type, + 'group_name' => $c->group_name, + 'sub_labels' => $c->sub_labels, + ]; + })->toArray(), + ]; + } +} \ No newline at end of file diff --git a/app/Models/DocumentTemplate.php b/app/Models/DocumentTemplate.php new file mode 100644 index 00000000..0a79e777 --- /dev/null +++ b/app/Models/DocumentTemplate.php @@ -0,0 +1,69 @@ + 'array', + 'is_active' => 'boolean', + ]; + + /** + * 결재라인 + */ + public function approvalLines(): HasMany + { + return $this->hasMany(DocumentTemplateApprovalLine::class, 'template_id') + ->orderBy('sort_order'); + } + + /** + * 기본 필드 + */ + public function basicFields(): HasMany + { + return $this->hasMany(DocumentTemplateBasicField::class, 'template_id') + ->orderBy('sort_order'); + } + + /** + * 검사 기준서 섹션 + */ + public function sections(): HasMany + { + return $this->hasMany(DocumentTemplateSection::class, 'template_id') + ->orderBy('sort_order'); + } + + /** + * 테이블 컬럼 + */ + public function columns(): HasMany + { + return $this->hasMany(DocumentTemplateColumn::class, 'template_id') + ->orderBy('sort_order'); + } +} \ No newline at end of file diff --git a/app/Models/DocumentTemplateApprovalLine.php b/app/Models/DocumentTemplateApprovalLine.php new file mode 100644 index 00000000..6997cdc8 --- /dev/null +++ b/app/Models/DocumentTemplateApprovalLine.php @@ -0,0 +1,29 @@ + 'integer', + ]; + + public function template(): BelongsTo + { + return $this->belongsTo(DocumentTemplate::class, 'template_id'); + } +} \ No newline at end of file diff --git a/app/Models/DocumentTemplateBasicField.php b/app/Models/DocumentTemplateBasicField.php new file mode 100644 index 00000000..0f2c3ef9 --- /dev/null +++ b/app/Models/DocumentTemplateBasicField.php @@ -0,0 +1,29 @@ + 'integer', + ]; + + public function template(): BelongsTo + { + return $this->belongsTo(DocumentTemplate::class, 'template_id'); + } +} \ No newline at end of file diff --git a/app/Models/DocumentTemplateColumn.php b/app/Models/DocumentTemplateColumn.php new file mode 100644 index 00000000..ab1a0c67 --- /dev/null +++ b/app/Models/DocumentTemplateColumn.php @@ -0,0 +1,32 @@ + 'array', + 'sort_order' => 'integer', + ]; + + public function template(): BelongsTo + { + return $this->belongsTo(DocumentTemplate::class, 'template_id'); + } +} \ No newline at end of file diff --git a/app/Models/DocumentTemplateSection.php b/app/Models/DocumentTemplateSection.php new file mode 100644 index 00000000..b808c774 --- /dev/null +++ b/app/Models/DocumentTemplateSection.php @@ -0,0 +1,35 @@ + 'integer', + ]; + + public function template(): BelongsTo + { + return $this->belongsTo(DocumentTemplate::class, 'template_id'); + } + + public function items(): HasMany + { + return $this->hasMany(DocumentTemplateSectionItem::class, 'section_id') + ->orderBy('sort_order'); + } +} \ No newline at end of file diff --git a/app/Models/DocumentTemplateSectionItem.php b/app/Models/DocumentTemplateSectionItem.php new file mode 100644 index 00000000..589f17aa --- /dev/null +++ b/app/Models/DocumentTemplateSectionItem.php @@ -0,0 +1,32 @@ + 'integer', + ]; + + public function section(): BelongsTo + { + return $this->belongsTo(DocumentTemplateSection::class, 'section_id'); + } +} \ No newline at end of file diff --git a/database/seeders/MngMenuSeeder.php b/database/seeders/MngMenuSeeder.php deleted file mode 100644 index 4ebd560c..00000000 --- a/database/seeders/MngMenuSeeder.php +++ /dev/null @@ -1,932 +0,0 @@ -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'], - ]); - $this->createMenu([ - 'parent_id' => $systemGroup->id, - 'name' => '메뉴 동기화', - 'url' => '/menus/sync', - 'icon' => 'refresh', - 'sort_order' => $systemSubOrder++, - 'options' => ['route_name' => 'menus.sync.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' => '/tenant-settings', - 'icon' => 'cog', - 'sort_order' => $prodSubOrder++, - 'options' => ['route_name' => 'tenant-settings.index', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $productionGroup->id, - 'name' => '공통코드 관리', - 'url' => '/common-codes', - 'icon' => 'collection', - 'sort_order' => $prodSubOrder++, - 'options' => ['route_name' => 'common-codes.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'], - ]); - - // ======================================== - // 재무관리 그룹 - // ======================================== - $financeGroup = $this->createMenu([ - 'name' => '재무관리', - 'url' => '#', - 'icon' => 'currency-dollar', - 'sort_order' => $sortOrder++, - 'options' => [ - 'section' => 'main', - 'meta' => ['group_id' => 'finance-group'], - ], - ]); - - $financeSubOrder = 0; - - // 대시보드/일보 - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '재무 대시보드', - 'url' => '/finance/dashboard', - 'icon' => 'chart-bar', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.dashboard', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '일일자금일보', - 'url' => '/finance/daily-fund', - 'icon' => 'newspaper', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.daily-fund', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '자금계획일정', - 'url' => '/finance/fund-schedules', - 'icon' => 'calendar', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.fund-schedules.index', 'section' => 'main'], - ]); - - // 계좌관리 - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '계좌관리', - 'url' => '/finance/accounts', - 'icon' => 'credit-card', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.accounts.index', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '계좌거래내역', - 'url' => '/finance/account-transactions', - 'icon' => 'document-text', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.account-transactions', 'section' => 'main'], - ]); - - // 카드관리 - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '법인카드관리', - 'url' => '/finance/corporate-cards', - 'icon' => 'credit-card', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.corporate-cards', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '카드사용내역', - 'url' => '/finance/card-transactions', - 'icon' => 'document-text', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.card-transactions', 'section' => 'main'], - ]); - - // 수입/지출 - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '수입관리', - 'url' => '/finance/income', - 'icon' => 'arrow-down', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.income', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '지출관리', - 'url' => '/finance/expense', - 'icon' => 'arrow-up', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.expense', 'section' => 'main'], - ]); - - // 매출/매입 - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '매출관리', - 'url' => '/finance/sales', - 'icon' => 'trending-up', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.sales', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '매입관리', - 'url' => '/finance/purchase', - 'icon' => 'shopping-cart', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.purchase', 'section' => 'main'], - ]); - - // 정산관리 - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '영업수수료 정산', - 'url' => '/finance/sales-commission', - 'icon' => 'cash', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.sales-commission', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '컨설팅비용 정산', - 'url' => '/finance/consulting-fee', - 'icon' => 'briefcase', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.consulting-fee', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '고객사 정산', - 'url' => '/finance/customer-settlement', - 'icon' => 'users', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.customer-settlement', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '구독료 정산', - 'url' => '/finance/subscription', - 'icon' => 'refresh', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.subscription', 'section' => 'main'], - ]); - - // 차량관리 - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '법인차량관리', - 'url' => '/finance/corporate-vehicles', - 'icon' => 'truck', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.corporate-vehicles', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '차량정비이력', - 'url' => '/finance/vehicle-maintenance', - 'icon' => 'wrench', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.vehicle-maintenance', 'section' => 'main'], - ]); - - // 거래처관리 - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '고객사관리', - 'url' => '/finance/customers', - 'icon' => 'user-group', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.customers', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '협력사관리', - 'url' => '/finance/partners', - 'icon' => 'office-building', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.partners', 'section' => 'main'], - ]); - - // 채권/채무 - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '채권관리', - 'url' => '/finance/receivables', - 'icon' => 'document-add', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.receivables', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '채무관리', - 'url' => '/finance/payables', - 'icon' => 'document-remove', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.payables', 'section' => 'main'], - ]); - - // 기타 - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '환불관리', - 'url' => '/finance/refunds', - 'icon' => 'receipt-refund', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.refunds', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $financeGroup->id, - 'name' => '부가세관리', - 'url' => '/finance/vat', - 'icon' => 'calculator', - 'sort_order' => $financeSubOrder++, - 'options' => ['route_name' => 'finance.vat', 'section' => 'main'], - ]); - - // ======================================== - // 바로빌본사 그룹 (본사 관리용) - // ======================================== - $barobillHqGroup = $this->createMenu([ - 'name' => '바로빌본사', - 'url' => '#', - 'icon' => 'office-building', - 'sort_order' => $sortOrder++, - 'options' => [ - 'section' => 'main', - 'meta' => ['group_id' => 'barobill-hq-group'], - ], - ]); - - $barobillHqSubOrder = 0; - $this->createMenu([ - 'parent_id' => $barobillHqGroup->id, - 'name' => '바로빌설정', - 'url' => '/barobill/config', - 'icon' => 'cog', - 'sort_order' => $barobillHqSubOrder++, - 'options' => ['route_name' => 'barobill.config.index', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $barobillHqGroup->id, - 'name' => '회원사관리', - 'url' => '/barobill/members', - 'icon' => 'building', - 'sort_order' => $barobillHqSubOrder++, - 'options' => ['route_name' => 'barobill.members.index', 'section' => 'main'], - ]); - - // ======================================== - // 바로빌 그룹 (회원사용) - // ======================================== - $barobillGroup = $this->createMenu([ - 'name' => '바로빌', - 'url' => '#', - 'icon' => 'receipt', - 'sort_order' => $sortOrder++, - 'options' => [ - 'section' => 'main', - 'meta' => ['group_id' => 'barobill-group'], - ], - ]); - - $barobillSubOrder = 0; - $this->createMenu([ - 'parent_id' => $barobillGroup->id, - 'name' => '설정', - 'url' => '/barobill/settings', - 'icon' => 'cog', - 'sort_order' => $barobillSubOrder++, - 'options' => ['route_name' => 'barobill.settings.index', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $barobillGroup->id, - 'name' => '전자세금계산서', - 'url' => '/barobill/etax', - 'icon' => 'document-text', - 'sort_order' => $barobillSubOrder++, - 'options' => ['route_name' => 'barobill.etax.index', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $barobillGroup->id, - 'name' => '계좌 입출금내역', - 'url' => '/barobill/eaccount', - 'icon' => 'credit-card', - 'sort_order' => $barobillSubOrder++, - 'options' => ['route_name' => 'barobill.eaccount.index', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $barobillGroup->id, - 'name' => '카드 사용내역', - 'url' => '/barobill/ecard', - 'icon' => 'credit-card', - 'sort_order' => $barobillSubOrder++, - 'options' => ['route_name' => 'barobill.ecard.index', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $barobillGroup->id, - 'name' => '홈텍스매입/매출', - 'url' => '/barobill/hometax', - 'icon' => 'document-report', - 'sort_order' => $barobillSubOrder++, - 'options' => ['route_name' => 'barobill.hometax.index', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $barobillGroup->id, - 'name' => '사용량조회', - 'url' => '/barobill/usage', - 'icon' => 'chart-bar', - 'sort_order' => $barobillSubOrder++, - 'options' => ['route_name' => 'barobill.usage.index', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $barobillGroup->id, - 'name' => '과금관리', - 'url' => '/barobill/billing', - 'icon' => 'currency-dollar', - 'sort_order' => $barobillSubOrder++, - 'options' => ['route_name' => 'barobill.billing.index', 'section' => 'main'], - ]); - - // ======================================== - // 신용평가 그룹 - // ======================================== - $creditGroup = $this->createMenu([ - 'name' => '신용평가', - 'url' => '#', - 'icon' => 'shield-check', - 'sort_order' => $sortOrder++, - 'options' => [ - 'section' => 'main', - 'meta' => ['group_id' => 'credit-group'], - ], - ]); - - $creditSubOrder = 0; - $this->createMenu([ - 'parent_id' => $creditGroup->id, - 'name' => '신용평가 조회', - 'url' => '/credit/inquiry', - 'icon' => 'search', - 'sort_order' => $creditSubOrder++, - 'options' => ['route_name' => 'credit.inquiry.index', 'section' => 'main'], - ]); - - // ======================================== - // 영업관리 그룹 - // ======================================== - $salesGroup = $this->createMenu([ - 'name' => '영업관리', - 'url' => '#', - 'icon' => 'briefcase', - 'sort_order' => $sortOrder++, - 'options' => [ - 'section' => 'main', - 'meta' => ['group_id' => 'sales-group'], - ], - ]); - - $salesSubOrder = 0; - $this->createMenu([ - 'parent_id' => $salesGroup->id, - 'name' => '영업담당자 관리', - 'url' => '/sales/managers', - 'icon' => 'users', - 'sort_order' => $salesSubOrder++, - 'options' => ['route_name' => 'sales.managers.index', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $salesGroup->id, - 'name' => '가망고객 관리', - 'url' => '/sales/prospects', - 'icon' => 'user-group', - 'sort_order' => $salesSubOrder++, - 'options' => ['route_name' => 'sales.prospects.index', 'section' => 'main'], - ]); - $this->createMenu([ - 'parent_id' => $salesGroup->id, - 'name' => '영업실적 관리', - 'url' => '/sales/records', - 'icon' => 'chart-bar', - 'sort_order' => $salesSubOrder++, - 'options' => ['route_name' => 'sales.records.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)); - } -} diff --git a/resources/views/common-codes/index.blade.php b/resources/views/common-codes/index.blade.php index 5b4a4242..434fac6c 100644 --- a/resources/views/common-codes/index.blade.php +++ b/resources/views/common-codes/index.blade.php @@ -92,14 +92,32 @@ class="px-4 py-3 text-sm font-medium border-b-2 whitespace-nowrap transition-col

글로벌 코드

({{ $globalCodes->count() }}) - @if(!$isHQ) - 본사만 편집 가능 - @endif +
+ @if($globalCodes->count() > 0) + + @endif + @if(!$isHQ) + 본사만 편집 가능 + @endif +
+ @@ -110,6 +128,11 @@ class="px-4 py-3 text-sm font-medium border-b-2 whitespace-nowrap transition-col @forelse($globalCodes as $code) + @@ -163,7 +186,7 @@ class="p-1 text-gray-400 hover:text-red-600 transition-colors" @empty - @@ -365,6 +388,12 @@ class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm fon @csrf + + + + @csrf + + @endsection @push('scripts') @@ -426,6 +455,59 @@ function copyCode(id) { form.submit(); } + // 전체 선택 토글 + function toggleSelectAll(checkbox) { + const checkboxes = document.querySelectorAll('.global-code-checkbox'); + checkboxes.forEach(cb => cb.checked = checkbox.checked); + updateBulkCopyButton(); + } + + // 선택된 항목 수 업데이트 및 버튼 표시/숨김 + function updateBulkCopyButton() { + const checkboxes = document.querySelectorAll('.global-code-checkbox:checked'); + const count = checkboxes.length; + const btn = document.getElementById('bulkCopyBtn'); + const countSpan = document.getElementById('bulkCopyCount'); + + if (btn) { + if (count > 0) { + btn.classList.remove('hidden'); + btn.classList.add('flex'); + countSpan.textContent = count; + } else { + btn.classList.add('hidden'); + btn.classList.remove('flex'); + } + } + + // 전체 선택 체크박스 상태 업데이트 + const allCheckboxes = document.querySelectorAll('.global-code-checkbox'); + const selectAllCheckbox = document.getElementById('selectAllGlobal'); + if (selectAllCheckbox) { + selectAllCheckbox.checked = allCheckboxes.length > 0 && checkboxes.length === allCheckboxes.length; + selectAllCheckbox.indeterminate = checkboxes.length > 0 && checkboxes.length < allCheckboxes.length; + } + } + + // 일괄 복사 + function bulkCopy() { + const checkboxes = document.querySelectorAll('.global-code-checkbox:checked'); + const ids = Array.from(checkboxes).map(cb => cb.value); + + if (ids.length === 0) { + alert('복사할 코드를 선택해주세요.'); + return; + } + + if (!confirm(`선택한 ${ids.length}개 글로벌 코드를 테넌트용으로 복사하시겠습니까?`)) return; + + // hidden form으로 POST + const form = document.getElementById('bulkCopyForm'); + const idsInput = document.getElementById('bulkCopyIds'); + idsInput.value = JSON.stringify(ids); + form.submit(); + } + // 코드 삭제 function deleteCode(id, code) { if (!confirm(`'${code}' 코드를 삭제하시겠습니까?`)) return; diff --git a/resources/views/department-permissions/partials/permission-matrix.blade.php b/resources/views/department-permissions/partials/permission-matrix.blade.php index 357e8206..3de478b3 100644 --- a/resources/views/department-permissions/partials/permission-matrix.blade.php +++ b/resources/views/department-permissions/partials/permission-matrix.blade.php @@ -74,8 +74,7 @@ class="toggle-btn flex items-center text-blue-500 hover:text-blue-700 focus:outl {{ isset($permissions[$menu->id][$type]) && $permissions[$menu->id][$type] ? 'checked' : '' }} class="h-5 w-5 rounded border-gray-300 text-primary focus:ring-primary cursor-pointer" hx-post="/api/admin/department-permissions/toggle" - hx-trigger="click" - hx-target="#permission-matrix" + hx-swap="none" hx-include="[name='department_id'],[name='guard_name']" hx-vals='{"menu_id": {{ $menu->id }}, "permission_type": "{{ $type }}"}' > diff --git a/resources/views/document-templates/edit.blade.php b/resources/views/document-templates/edit.blade.php new file mode 100644 index 00000000..0d3f02db --- /dev/null +++ b/resources/views/document-templates/edit.blade.php @@ -0,0 +1,845 @@ +@extends('layouts.app') + +@section('title', $isCreate ? '문서양식 등록' : '문서양식 편집') + +@section('content') +
+ +
+
+

+ {{ $isCreate ? '문서양식 등록' : '문서양식 편집' }} +

+

+ 검사 성적서, 작업지시서 등의 문서 양식을 설정합니다. +

+
+
+ + + 목록 + + +
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ + + + + + + + + +
+ + + +@endsection + +@push('scripts') + + + + +@endpush \ No newline at end of file diff --git a/resources/views/document-templates/index.blade.php b/resources/views/document-templates/index.blade.php new file mode 100644 index 00000000..deb85370 --- /dev/null +++ b/resources/views/document-templates/index.blade.php @@ -0,0 +1,165 @@ +@extends('layouts.app') + +@section('title', '문서양식 관리') + +@section('content') + +
+
+

문서양식 관리

+ +
+ +
+ + + +
+ + + + +
+ +
+ + +
+ +
+ + +
+ +
+ + + + +
+ + +
+ +
+
+
+
+@endsection + +@push('scripts') + +@endpush \ No newline at end of file diff --git a/resources/views/document-templates/partials/table.blade.php b/resources/views/document-templates/partials/table.blade.php new file mode 100644 index 00000000..40e7ff6c --- /dev/null +++ b/resources/views/document-templates/partials/table.blade.php @@ -0,0 +1,102 @@ +
+
+ + 코드 이름 순서
+ + {{ $code->code }}
+ 글로벌 코드가 없습니다.
+ + + + + + + + + + + + + + @forelse($templates as $template) + + + + + + + + + + + @empty + + + + @endforelse + +
양식명분류문서 제목섹션컬럼활성수정일작업
+ + {{ $template->name }} + + + @if($template->category) + + {{ $template->category }} + + @else + - + @endif + + {{ $template->title ?: '-' }} + + {{ $template->sections_count ?? 0 }} + + {{ $template->columns_count ?? 0 }} + + + + {{ $template->updated_at->format('Y-m-d') }} + +
+ + + + + + +
+
+
+ + + +

등록된 문서양식이 없습니다.

+ + + 새 양식 만들기 + +
+
+
+ + +@if($templates->hasPages()) +
+ @include('partials.pagination', ['paginator' => $templates, 'htmxTarget' => '#template-table', 'htmxTrigger' => 'filterSubmit']) +
+@endif \ No newline at end of file diff --git a/resources/views/role-permissions/partials/permission-matrix.blade.php b/resources/views/role-permissions/partials/permission-matrix.blade.php index bf576594..a6bfa256 100644 --- a/resources/views/role-permissions/partials/permission-matrix.blade.php +++ b/resources/views/role-permissions/partials/permission-matrix.blade.php @@ -74,7 +74,7 @@ class="toggle-btn flex items-center text-blue-500 hover:text-blue-700 focus:outl {{ isset($permissions[$menu->id][$type]) && $permissions[$menu->id][$type] ? 'checked' : '' }} class="h-5 w-5 rounded border-gray-300 text-primary focus:ring-primary cursor-pointer" hx-post="/api/admin/role-permissions/toggle" - hx-target="#permission-matrix" + hx-swap="none" hx-include="[name='role_id'],[name='guard_name']" hx-vals='{"menu_id": {{ $menu->id }}, "permission_type": "{{ $type }}"}' > diff --git a/routes/api.php b/routes/api.php index 6fd4561f..cb165b34 100644 --- a/routes/api.php +++ b/routes/api.php @@ -3,7 +3,7 @@ use App\Http\Controllers\Api\Admin\BoardController; use App\Http\Controllers\Api\Admin\CustomerCenterController; use App\Http\Controllers\Api\Admin\DailyLogController; -use App\Http\Controllers\Api\Admin\DepartmentController; +use App\Http\Controllers\Api\Admin\DepartmentController;use App\Http\Controllers\Api\Admin\DocumentTemplateApiController; use App\Http\Controllers\Api\Admin\GlobalMenuController; use App\Http\Controllers\Api\Admin\ItemFieldController; use App\Http\Controllers\Api\Admin\MeetingLogController; @@ -772,6 +772,21 @@ Route::delete('/{id}', [\App\Http\Controllers\Api\BizCertController::class, 'destroy'])->name('destroy'); }); +/* +|-------------------------------------------------------------------------- +| 문서양식 관리 API +|-------------------------------------------------------------------------- +*/ +Route::middleware(['web', 'auth', 'hq.member'])->prefix('admin/document-templates')->name('api.admin.document-templates.')->group(function () { + Route::get('/', [DocumentTemplateApiController::class, 'index'])->name('index'); + Route::post('/', [DocumentTemplateApiController::class, 'store'])->name('store'); + Route::get('/{id}', [DocumentTemplateApiController::class, 'show'])->name('show'); + Route::put('/{id}', [DocumentTemplateApiController::class, 'update'])->name('update'); + Route::delete('/{id}', [DocumentTemplateApiController::class, 'destroy'])->name('destroy'); + Route::post('/{id}/toggle-active', [DocumentTemplateApiController::class, 'toggleActive'])->name('toggle-active'); + Route::post('/upload-image', [DocumentTemplateApiController::class, 'uploadImage'])->name('upload-image'); +}); + /* |-------------------------------------------------------------------------- | 웹 녹음 AI 요약 API diff --git a/routes/web.php b/routes/web.php index 9688e559..0d60a960 100644 --- a/routes/web.php +++ b/routes/web.php @@ -26,6 +26,7 @@ use App\Http\Controllers\TenantController; use App\Http\Controllers\TenantSettingController; use App\Http\Controllers\CommonCodeController; +use App\Http\Controllers\DocumentTemplateController; use App\Http\Controllers\MenuSyncController; use App\Http\Controllers\UserController; use Illuminate\Support\Facades\Route; @@ -301,12 +302,20 @@ Route::prefix('common-codes')->name('common-codes.')->group(function () { Route::get('/', [CommonCodeController::class, 'index'])->name('index'); Route::post('/', [CommonCodeController::class, 'store'])->name('store'); + Route::post('/bulk-copy', [CommonCodeController::class, 'bulkCopy'])->name('bulk-copy'); Route::put('/{id}', [CommonCodeController::class, 'update'])->name('update'); Route::post('/{id}/toggle', [CommonCodeController::class, 'toggle'])->name('toggle'); Route::post('/{id}/copy', [CommonCodeController::class, 'copy'])->name('copy'); Route::delete('/{id}', [CommonCodeController::class, 'destroy'])->name('destroy'); }); + // 문서양식 관리 + Route::prefix('document-templates')->name('document-templates.')->group(function () { + Route::get('/', [DocumentTemplateController::class, 'index'])->name('index'); + Route::get('/create', [DocumentTemplateController::class, 'create'])->name('create'); + Route::get('/{id}/edit', [DocumentTemplateController::class, 'edit'])->name('edit'); + }); + /* |-------------------------------------------------------------------------- | 바로빌 Routes