systemOnly() ->withCount('fields') ->withTrashed(); // 검색 필터 if (! empty($filters['search'])) { $search = $filters['search']; $query->where(function ($q) use ($search) { $q->where('name', 'like', "%{$search}%") ->orWhere('board_code', 'like', "%{$search}%") ->orWhere('description', 'like', "%{$search}%"); }); } // 게시판 유형 필터 if (! empty($filters['board_type'])) { $query->where('board_type', $filters['board_type']); } // 활성 상태 필터 if (isset($filters['is_active']) && $filters['is_active'] !== '') { $query->where('is_active', $filters['is_active']); } // Soft Delete 필터 if (isset($filters['trashed'])) { if ($filters['trashed'] === 'only') { $query->onlyTrashed(); } elseif ($filters['trashed'] === 'with') { $query->withTrashed(); } } // 정렬 $sortBy = $filters['sort_by'] ?? 'id'; $sortDirection = $filters['sort_direction'] ?? 'desc'; $query->orderBy($sortBy, $sortDirection); return $query->paginate($perPage); } /** * 시스템 게시판 목록 (드롭다운용) */ public function getActiveBoardList(): Collection { return Board::query() ->systemOnly() ->active() ->orderBy('name') ->get(['id', 'board_code', 'name', 'board_type']); } /** * 특정 게시판 조회 * * @param bool $systemOnly true면 시스템 게시판만, false면 모든 게시판 */ public function getBoardById(int $id, bool $withTrashed = false, bool $systemOnly = true): ?Board { $query = Board::query() ->with(['fields', 'tenant:id,code,company_name']) ->withCount('fields'); if ($systemOnly) { $query->systemOnly(); } if ($withTrashed) { $query->withTrashed(); } return $query->find($id); } /** * 게시판 생성 */ public function createBoard(array $data): Board { // 시스템 게시판 설정 $data['is_system'] = true; $data['tenant_id'] = null; $data['created_by'] = auth()->id(); $board = Board::create($data); // 메뉴 자동 생성 $this->menuService->createMenuForBoard([ 'board_code' => $board->board_code, 'name' => $board->name, 'is_system' => true, 'tenant_id' => null, ]); return $board; } /** * 게시판 수정 */ public function updateBoard(int $id, array $data): bool { $board = Board::systemOnly()->findOrFail($id); $data['updated_by'] = auth()->id(); return $board->update($data); } /** * 게시판 삭제 (Soft Delete) */ public function deleteBoard(int $id): bool { $board = Board::systemOnly()->findOrFail($id); $board->deleted_by = auth()->id(); $board->save(); return $board->delete(); } /** * 게시판 복원 */ public function restoreBoard(int $id): bool { $board = Board::systemOnly()->onlyTrashed()->findOrFail($id); $board->deleted_by = null; return $board->restore(); } /** * 게시판 영구 삭제 */ public function forceDeleteBoard(int $id): bool { $board = Board::systemOnly()->withTrashed()->findOrFail($id); // 관련 필드 삭제 $board->fields()->delete(); return $board->forceDelete(); } /** * 게시판 코드 중복 체크 */ public function isCodeExists(string $code, ?int $excludeId = null): bool { $query = Board::where('board_code', $code) ->where('is_system', true); if ($excludeId) { $query->where('id', '!=', $excludeId); } return $query->exists(); } /** * 게시판 활성/비활성 토글 */ public function toggleActive(int $id): Board { $board = Board::systemOnly()->findOrFail($id); $board->is_active = ! $board->is_active; $board->updated_by = auth()->id(); $board->save(); return $board; } /** * 게시판 통계 */ public function getBoardStats(): array { return [ 'total' => Board::systemOnly()->count(), 'active' => Board::systemOnly()->active()->count(), 'inactive' => Board::systemOnly()->where('is_active', false)->count(), 'trashed' => Board::systemOnly()->onlyTrashed()->count(), ]; } // ========================================================================= // 필드 관리 // ========================================================================= /** * 게시판 필드 목록 조회 */ public function getBoardFields(int $boardId): Collection { return BoardSetting::where('board_id', $boardId) ->orderBy('sort_order') ->get(); } /** * 게시판 필드 추가 */ public function addBoardField(int $boardId, array $data): BoardSetting { $data['board_id'] = $boardId; $data['created_by'] = auth()->id(); // 기본 정렬 순서 설정 if (! isset($data['sort_order'])) { $maxOrder = BoardSetting::where('board_id', $boardId)->max('sort_order') ?? 0; $data['sort_order'] = $maxOrder + 1; } return BoardSetting::create($data); } /** * 게시판 필드 수정 */ public function updateBoardField(int $fieldId, array $data): bool { $field = BoardSetting::findOrFail($fieldId); $data['updated_by'] = auth()->id(); return $field->update($data); } /** * 게시판 필드 삭제 */ public function deleteBoardField(int $fieldId): bool { $field = BoardSetting::findOrFail($fieldId); return $field->delete(); } /** * 게시판 필드 순서 변경 */ public function reorderBoardFields(int $boardId, array $fieldIds): bool { foreach ($fieldIds as $order => $fieldId) { BoardSetting::where('id', $fieldId) ->where('board_id', $boardId) ->update(['sort_order' => $order + 1]); } return true; } /** * 게시판 유형 목록 (사용 중인 유형들) */ public function getBoardTypes(): array { return Board::systemOnly() ->whereNotNull('board_type') ->distinct() ->pluck('board_type') ->toArray(); } // ========================================================================= // 템플릿 관리 // ========================================================================= /** * 템플릿 목록 조회 */ public function getTemplates(string $type = 'all'): array { $templates = config('board_templates', []); if ($type === 'system') { return $templates['system'] ?? []; } if ($type === 'tenant') { return $templates['tenant'] ?? []; } return [ 'system' => $templates['system'] ?? [], 'tenant' => $templates['tenant'] ?? [], ]; } /** * 특정 템플릿 조회 */ public function getTemplate(string $type, string $key): ?array { $templates = $this->getTemplates($type); return $templates[$key] ?? null; } /** * 기본 필드 목록 조회 */ public function getBaseFields(): array { return config('board_templates.base_fields', []); } /** * 템플릿 기반 게시판 생성 * * @param bool $createMenu 메뉴 자동 생성 여부 (기본: true) */ public function createBoardFromTemplate(array $data, ?string $templateType = null, ?string $templateKey = null, bool $createMenu = true): Board { return DB::transaction(function () use ($data, $templateType, $templateKey, $createMenu) { // 템플릿 설정 적용 $template = null; if ($templateType && $templateKey) { $template = $this->getTemplate($templateType, $templateKey); if ($template) { // 템플릿 기본값 적용 (사용자 입력값 우선) $data = array_merge([ 'board_type' => $template['board_type'] ?? null, 'editor_type' => $template['editor_type'] ?? 'wysiwyg', 'allow_files' => $template['allow_files'] ?? true, 'max_file_count' => $template['max_file_count'] ?? 5, 'max_file_size' => $template['max_file_size'] ?? 20480, 'extra_settings' => $template['extra_settings'] ?? [], ], $data); } } // 시스템/테넌트 구분 if (isset($data['tenant_id']) && $data['tenant_id']) { // 테넌트 게시판 $data['is_system'] = false; } else { // 시스템 게시판 $data['is_system'] = true; $data['tenant_id'] = null; } $data['created_by'] = auth()->id(); // 게시판 생성 $board = Board::create($data); // 템플릿 기본 필드 생성 if ($template && ! empty($template['default_fields'])) { foreach ($template['default_fields'] as $index => $field) { $field['board_id'] = $board->id; $field['sort_order'] = $index + 1; $field['created_by'] = auth()->id(); BoardSetting::create($field); } } // 메뉴 자동 생성 if ($createMenu) { $this->menuService->createMenuForBoard([ 'board_code' => $board->board_code, 'name' => $board->name, 'is_system' => $board->is_system, 'tenant_id' => $board->tenant_id, ]); } return $board->load('fields'); }); } // ========================================================================= // 테넌트 게시판 관리 // ========================================================================= /** * 모든 게시판 목록 조회 (시스템 + 테넌트) */ public function getAllBoards(array $filters = [], int $perPage = 15): LengthAwarePaginator { $query = Board::query() ->with('tenant:id,code,company_name') ->withCount(['fields', 'posts']) ->withTrashed(); // 헤더에서 선택한 테넌트 기준 $selectedTenantId = session('selected_tenant_id'); if ($selectedTenantId && $selectedTenantId !== 'all') { // 선택된 테넌트가 본사(HQ)인지 확인 $isHQ = Tenant::where('id', $selectedTenantId) ->where('tenant_type', 'HQ') ->exists(); if ($isHQ) { // 본사: 시스템 게시판 + 본사 테넌트 게시판 $query->where(function ($q) use ($selectedTenantId) { $q->where('is_system', true) ->orWhere('tenant_id', $selectedTenantId); }); } else { // 일반 테넌트: 해당 테넌트 게시판만 (시스템 게시판 제외) $query->where('tenant_id', $selectedTenantId); } } else { // 전체 보기: 시스템 게시판만 (테넌트 게시판은 테넌트 선택 후 표시) $query->where('is_system', true); } // 검색 필터 if (! empty($filters['search'])) { $search = $filters['search']; $query->where(function ($q) use ($search) { $q->where('name', 'like', "%{$search}%") ->orWhere('board_code', 'like', "%{$search}%") ->orWhere('description', 'like', "%{$search}%"); }); } // 게시판 유형 필터 if (! empty($filters['board_type'])) { $query->where('board_type', $filters['board_type']); } // 활성 상태 필터 if (isset($filters['is_active']) && $filters['is_active'] !== '') { $query->where('is_active', $filters['is_active']); } // Soft Delete 필터 if (isset($filters['trashed'])) { if ($filters['trashed'] === 'only') { $query->onlyTrashed(); } elseif ($filters['trashed'] === 'with') { $query->withTrashed(); } } // 정렬: 시스템 게시판 우선, 그 다음 사용자 지정 정렬 $sortBy = $filters['sort_by'] ?? 'id'; $sortDirection = $filters['sort_direction'] ?? 'desc'; $query->orderBy('is_system', 'desc') // 시스템 게시판 우선 ->orderBy($sortBy, $sortDirection); return $query->paginate($perPage); } /** * 테넌트 목록 조회 (드롭다운용) */ public function getTenantList(): Collection { return Tenant::query() ->active() ->orderBy('company_name') ->get(['id', 'code', 'company_name']); } /** * 테넌트 게시판 코드 중복 체크 */ public function isTenantCodeExists(string $code, int $tenantId, ?int $excludeId = null): bool { $query = Board::where('board_code', $code) ->where('tenant_id', $tenantId); if ($excludeId) { $query->where('id', '!=', $excludeId); } return $query->exists(); } /** * 게시판 상세 조회 (시스템/테넌트 공통) */ public function getAnyBoardById(int $id, bool $withTrashed = false): ?Board { $query = Board::query() ->with('fields') ->withCount('fields'); if ($withTrashed) { $query->withTrashed(); } return $query->find($id); } /** * 게시판 수정 (시스템/테넌트 공통) */ public function updateAnyBoard(int $id, array $data): bool { $board = Board::findOrFail($id); // 기존 값 저장 (메뉴 업데이트용) $oldCode = $board->board_code; $oldName = $board->name; $data['updated_by'] = auth()->id(); // tenant_id 변경 방지 (시스템 ↔ 테넌트 전환 불가) unset($data['tenant_id'], $data['is_system']); $result = $board->update($data); // board_code 또는 name 변경 시 메뉴도 업데이트 $newCode = $data['board_code'] ?? $oldCode; $newName = $data['name'] ?? $oldName; if ($oldCode !== $newCode || $oldName !== $newName) { $this->menuService->updateMenuForBoard( $oldCode, $newCode, $newName, $board->is_system, $board->tenant_id ); } return $result; } /** * 게시판 삭제 (시스템/테넌트 공통, Soft Delete) * - 연결된 메뉴도 함께 Soft Delete */ public function deleteAnyBoard(int $id): bool { $board = Board::findOrFail($id); $board->deleted_by = auth()->id(); $board->save(); // 연결된 메뉴도 함께 삭제 (Soft Delete) $this->menuService->deleteMenuForBoard( $board->board_code, $board->is_system, $board->tenant_id ); return $board->delete(); } /** * 게시판 복원 (시스템/테넌트 공통) * - 연결된 메뉴도 함께 복원 */ public function restoreAnyBoard(int $id): bool { $board = Board::onlyTrashed()->findOrFail($id); $board->deleted_by = null; // 게시판 복원 $result = $board->restore(); // 연결된 메뉴도 복원 (없으면 생성) if ($result) { $this->menuService->restoreMenuForBoard( $board->board_code, $board->name, $board->is_system, $board->tenant_id ); } return $result; } /** * 게시판 영구 삭제 (시스템/테넌트 공통) * - 연결된 메뉴도 함께 영구 삭제 */ public function forceDeleteAnyBoard(int $id): bool { $board = Board::withTrashed()->findOrFail($id); // 관련 필드 삭제 $board->fields()->delete(); // 연결된 메뉴도 함께 영구 삭제 $this->menuService->deleteMenuForBoard( $board->board_code, $board->is_system, $board->tenant_id, true // forceDelete ); return $board->forceDelete(); } }