diff --git a/backend-node/src/controllers/commonCodeController.ts b/backend-node/src/controllers/commonCodeController.ts index 40579b7e..b0db2059 100644 --- a/backend-node/src/controllers/commonCodeController.ts +++ b/backend-node/src/controllers/commonCodeController.ts @@ -275,22 +275,8 @@ export class CommonCodeController { const { categoryCode } = req.params; const codeData: CreateCodeData = req.body; const userId = req.user?.userId || "SYSTEM"; - // 사용자의 회사 코드 사용 (회사별 코드 관리) const companyCode = req.user?.companyCode || "*"; - - // 카테고리 조회 - 존재 여부 확인 및 menuObjid 가져오기 - const category = await this.commonCodeService.getCategoryByCode(categoryCode); - if (!category) { - return res.status(404).json({ - success: false, - message: `카테고리를 찾을 수 없습니다: ${categoryCode}`, - }); - } - - // 카테고리의 menuObjid 사용 (카테고리는 공통, 코드는 회사별) - const menuObjid = category.menu_objid; - - logger.info(`코드 생성 - 사용자 회사로 저장: categoryCode=${categoryCode}, companyCode=${companyCode}, menuObjid=${menuObjid}`); + const menuObjid = req.body.menuObjid; // 입력값 검증 if (!codeData.codeValue || !codeData.codeName) { @@ -300,6 +286,13 @@ export class CommonCodeController { }); } + if (!menuObjid) { + return res.status(400).json({ + success: false, + message: "메뉴 OBJID는 필수입니다.", + }); + } + const code = await this.commonCodeService.createCode( categoryCode, codeData, @@ -595,100 +588,4 @@ export class CommonCodeController { }); } } - - /** - * 테이블.컬럼 기반 카테고리 옵션 조회 (레거시 호환용) - * GET /api/common-codes/category-options/:tableName/:columnName - * - * 기존 카테고리 타입이 공통코드로 마이그레이션된 후에도 - * 기존 API 호출이 동작하도록 호환성 제공 - */ - async getCategoryOptionsAsCode(req: AuthenticatedRequest, res: Response) { - try { - const { tableName, columnName } = req.params; - const userCompanyCode = req.user?.companyCode; - - logger.info(`카테고리 → 공통코드 호환 조회: ${tableName}.${columnName}`); - - const options = await this.commonCodeService.getCategoryOptionsAsCode( - tableName, - columnName, - userCompanyCode - ); - - return res.json({ - success: true, - data: options, - message: `카테고리 옵션 조회 성공 (${tableName}.${columnName})`, - }); - } catch (error) { - logger.error(`카테고리 옵션 조회 실패 (${req.params.tableName}.${req.params.columnName}):`, error); - return res.status(500).json({ - success: false, - message: "카테고리 옵션 조회 중 오류가 발생했습니다.", - error: error instanceof Error ? error.message : "Unknown error", - }); - } - } - - /** - * 계층 구조 코드 조회 (트리 형태) - * GET /api/common-codes/categories/:categoryCode/hierarchy - */ - async getCodesHierarchy(req: AuthenticatedRequest, res: Response) { - try { - const { categoryCode } = req.params; - const userCompanyCode = req.user?.companyCode; - - const hierarchy = await this.commonCodeService.getCodesHierarchy( - categoryCode, - userCompanyCode - ); - - return res.json({ - success: true, - data: hierarchy, - message: `계층 코드 조회 성공 (${categoryCode})`, - }); - } catch (error) { - logger.error(`계층 코드 조회 실패 (${req.params.categoryCode}):`, error); - return res.status(500).json({ - success: false, - message: "계층 코드 조회 중 오류가 발생했습니다.", - error: error instanceof Error ? error.message : "Unknown error", - }); - } - } - - /** - * 자식 코드 조회 (연쇄 선택용) - * GET /api/common-codes/categories/:categoryCode/children - * Query: parentCodeValue (optional) - */ - async getChildCodes(req: AuthenticatedRequest, res: Response) { - try { - const { categoryCode } = req.params; - const { parentCodeValue } = req.query; - const userCompanyCode = req.user?.companyCode; - - const children = await this.commonCodeService.getChildCodes( - categoryCode, - parentCodeValue as string | null, - userCompanyCode - ); - - return res.json({ - success: true, - data: children, - message: `자식 코드 조회 성공 (${categoryCode}, 부모: ${parentCodeValue || '최상위'})`, - }); - } catch (error) { - logger.error(`자식 코드 조회 실패 (${req.params.categoryCode}):`, error); - return res.status(500).json({ - success: false, - message: "자식 코드 조회 중 오류가 발생했습니다.", - error: error instanceof Error ? error.message : "Unknown error", - }); - } - } } diff --git a/backend-node/src/services/commonCodeService.ts b/backend-node/src/services/commonCodeService.ts index c59e17d3..db19adc3 100644 --- a/backend-node/src/services/commonCodeService.ts +++ b/backend-node/src/services/commonCodeService.ts @@ -25,9 +25,6 @@ export interface CodeInfo { is_active: string; company_code: string; menu_objid?: number | null; // 메뉴 기반 코드 관리용 - // 계층 구조 지원 - parent_code_value?: string | null; // 부모 코드 값 - depth?: number; // 계층 깊이 (1: 최상위) created_date?: Date | null; created_by?: string | null; updated_date?: Date | null; @@ -64,9 +61,6 @@ export interface CreateCodeData { description?: string; sortOrder?: number; isActive?: string; - // 계층 구조 지원 - parentCodeValue?: string; - depth?: number; } export class CommonCodeService { @@ -154,22 +148,6 @@ export class CommonCodeService { } } - /** - * 특정 카테고리 조회 (코드로) - */ - async getCategoryByCode(categoryCode: string): Promise { - try { - const category = await queryOne( - `SELECT * FROM code_category WHERE category_code = $1`, - [categoryCode] - ); - return category || null; - } catch (error) { - logger.error(`카테고리 조회 실패 (${categoryCode}):`, error); - return null; - } - } - /** * 카테고리별 코드 목록 조회 */ @@ -214,12 +192,11 @@ export class CommonCodeService { } // 회사별 필터링 (최고 관리자가 아닌 경우) - // company_code = '*'인 공통 데이터도 함께 조회 if (userCompanyCode && userCompanyCode !== "*") { - whereConditions.push(`(company_code = $${paramIndex} OR company_code = '*')`); + whereConditions.push(`company_code = $${paramIndex}`); values.push(userCompanyCode); paramIndex++; - logger.info(`회사별 코드 필터링: ${userCompanyCode} (공통 데이터 포함)`); + logger.info(`회사별 코드 필터링: ${userCompanyCode}`); } else if (userCompanyCode === "*") { logger.info(`최고 관리자: 모든 코드 조회`); } @@ -428,22 +405,11 @@ export class CommonCodeService { menuObjid: number ) { try { - // 계층 구조 깊이 계산 - let depth = data.depth || 1; - if (data.parentCodeValue && !data.depth) { - // 부모 코드의 depth를 조회하여 +1 - const parentCode = await queryOne<{ depth: number }>( - `SELECT depth FROM code_info WHERE code_category = $1 AND code_value = $2`, - [categoryCode, data.parentCodeValue] - ); - depth = (parentCode?.depth || 0) + 1; - } - const code = await queryOne( `INSERT INTO code_info (code_category, code_value, code_name, code_name_eng, description, sort_order, - is_active, menu_objid, company_code, parent_code_value, depth, created_by, updated_by, created_date, updated_date) - VALUES ($1, $2, $3, $4, $5, $6, 'Y', $7, $8, $9, $10, $11, $12, NOW(), NOW()) + is_active, menu_objid, company_code, created_by, updated_by, created_date, updated_date) + VALUES ($1, $2, $3, $4, $5, $6, 'Y', $7, $8, $9, $10, NOW(), NOW()) RETURNING *`, [ categoryCode, @@ -454,15 +420,13 @@ export class CommonCodeService { data.sortOrder || 0, menuObjid, companyCode, - data.parentCodeValue || null, - depth, createdBy, createdBy, ] ); logger.info( - `코드 생성 완료: ${categoryCode}.${data.codeValue} (메뉴: ${menuObjid}, 회사: ${companyCode}, 부모: ${data.parentCodeValue || '없음'}, depth: ${depth})` + `코드 생성 완료: ${categoryCode}.${data.codeValue} (메뉴: ${menuObjid}, 회사: ${companyCode})` ); return code; } catch (error) { @@ -527,25 +491,6 @@ export class CommonCodeService { updateFields.push(`is_active = $${paramIndex++}`); values.push(activeValue); } - // 계층 구조 필드 - if (data.parentCodeValue !== undefined) { - updateFields.push(`parent_code_value = $${paramIndex++}`); - values.push(data.parentCodeValue || null); - - // depth 자동 계산 - if (data.parentCodeValue) { - const parentCode = await queryOne<{ depth: number }>( - `SELECT depth FROM code_info WHERE code_category = $1 AND code_value = $2`, - [categoryCode, data.parentCodeValue] - ); - const newDepth = (parentCode?.depth || 0) + 1; - updateFields.push(`depth = $${paramIndex++}`); - values.push(newDepth); - } else { - updateFields.push(`depth = $${paramIndex++}`); - values.push(1); // 부모 없으면 최상위 - } - } // WHERE 절 구성 let whereClause = `WHERE code_category = $${paramIndex++} AND code_value = $${paramIndex}`; @@ -649,164 +594,6 @@ export class CommonCodeService { } } - /** - * 계층 구조로 코드 조회 (트리 형태) - */ - async getCodesHierarchy( - categoryCode: string, - userCompanyCode?: string - ): Promise { - try { - let sql = ` - SELECT code_value, code_name, code_name_eng, description, sort_order, - is_active, parent_code_value, depth - FROM code_info - WHERE code_category = $1 AND is_active = 'Y' - `; - const values: any[] = [categoryCode]; - - // 회사별 필터링 - if (userCompanyCode && userCompanyCode !== "*") { - sql += ` AND company_code = $2`; - values.push(userCompanyCode); - } - - sql += ` ORDER BY depth ASC, sort_order ASC, code_value ASC`; - - const codes = await query<{ - code_value: string; - code_name: string; - code_name_eng: string | null; - description: string | null; - sort_order: number; - is_active: string; - parent_code_value: string | null; - depth: number; - }>(sql, values); - - // 트리 구조로 변환 - const codeMap = new Map(); - const roots: any[] = []; - - // 모든 코드를 맵에 저장 - for (const code of codes) { - codeMap.set(code.code_value, { - value: code.code_value, - label: code.code_name, - labelEng: code.code_name_eng, - description: code.description, - depth: code.depth || 1, - parentValue: code.parent_code_value, - children: [], - }); - } - - // 부모-자식 관계 구성 - for (const code of codes) { - const node = codeMap.get(code.code_value); - if (code.parent_code_value && codeMap.has(code.parent_code_value)) { - const parent = codeMap.get(code.parent_code_value); - parent.children.push(node); - } else { - roots.push(node); - } - } - - logger.info( - `계층 코드 조회 완료: ${categoryCode} - ${roots.length}개 루트, 전체 ${codes.length}개` - ); - return roots; - } catch (error) { - logger.error(`계층 코드 조회 중 오류 (${categoryCode}):`, error); - throw error; - } - } - - /** - * 특정 부모 코드의 자식 코드 목록 조회 (연쇄 선택용) - */ - async getChildCodes( - categoryCode: string, - parentCodeValue: string | null, - userCompanyCode?: string - ): Promise> { - try { - let sql = ` - SELECT - c.code_value, - c.code_name, - c.code_name_eng, - EXISTS(SELECT 1 FROM code_info c2 WHERE c2.parent_code_value = c.code_value AND c2.code_category = c.code_category) as has_children - FROM code_info c - WHERE c.code_category = $1 - AND c.is_active = 'Y' - `; - const values: any[] = [categoryCode]; - let paramIndex = 2; - - // 부모 코드 필터 - if (parentCodeValue) { - sql += ` AND c.parent_code_value = $${paramIndex++}`; - values.push(parentCodeValue); - } else { - sql += ` AND (c.parent_code_value IS NULL OR c.depth = 1)`; - } - - // 회사별 필터링 - if (userCompanyCode && userCompanyCode !== "*") { - sql += ` AND c.company_code = $${paramIndex++}`; - values.push(userCompanyCode); - } - - sql += ` ORDER BY c.sort_order ASC, c.code_value ASC`; - - const codes = await query<{ - code_value: string; - code_name: string; - code_name_eng: string | null; - has_children: boolean; - }>(sql, values); - - const result = codes.map((code) => ({ - value: code.code_value, - label: code.code_name, - hasChildren: code.has_children, - })); - - logger.info( - `자식 코드 조회 완료: ${categoryCode} (부모: ${parentCodeValue || '최상위'}) - ${result.length}개` - ); - return result; - } catch (error) { - logger.error(`자식 코드 조회 중 오류 (${categoryCode}, ${parentCodeValue}):`, error); - throw error; - } - } - - /** - * 테이블.컬럼 기반 카테고리 옵션 조회 (레거시 카테고리 호환용) - * - * 마이그레이션 후 category 타입이 code로 변환되었을 때 - * 기존 `tableName_columnName` 형식의 codeGroup으로 조회 - * - * @param tableName 테이블명 - * @param columnName 컬럼명 - * @param userCompanyCode 회사 코드 - */ - async getCategoryOptionsAsCode(tableName: string, columnName: string, userCompanyCode?: string) { - try { - // 카테고리 코드 그룹명 생성: TABLENAME_COLUMNNAME - const categoryCode = `${tableName.toUpperCase()}_${columnName.toUpperCase()}`; - - logger.info(`카테고리 → 코드 호환 조회: ${tableName}.${columnName} → ${categoryCode}`); - - return await this.getCodeOptions(categoryCode, userCompanyCode); - } catch (error) { - logger.error(`카테고리 호환 조회 중 오류 (${tableName}.${columnName}):`, error); - throw error; - } - } - /** * 코드 순서 변경 */ diff --git a/frontend/components/admin/CodeFormModal.tsx b/frontend/components/admin/CodeFormModal.tsx index 67d6c844..977e9e84 100644 --- a/frontend/components/admin/CodeFormModal.tsx +++ b/frontend/components/admin/CodeFormModal.tsx @@ -9,7 +9,6 @@ import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { LoadingSpinner } from "@/components/common/LoadingSpinner"; import { ValidationMessage } from "@/components/common/ValidationMessage"; @@ -84,9 +83,6 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code // 폼 스키마 선택 (생성/수정에 따라) const schema = isEditing ? updateCodeSchema : createCodeSchema; - // 부모 코드 선택 상태 - const [parentCodeValue, setParentCodeValue] = useState(""); - const form = useForm({ resolver: zodResolver(schema), mode: "onChange", // 실시간 검증 활성화 @@ -115,9 +111,6 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code // codeValue는 별도로 설정 (표시용) form.setValue("codeValue" as any, editingCode.codeValue || editingCode.code_value); - - // 부모 코드 설정 - setParentCodeValue(editingCode.parentCodeValue || editingCode.parent_code_value || ""); } else { // 새 코드 모드: 자동 순서 계산 const maxSortOrder = codes.length > 0 ? Math.max(...codes.map((c) => c.sortOrder || c.sort_order)) : 0; @@ -129,7 +122,6 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code description: "", sortOrder: maxSortOrder + 1, }); - setParentCodeValue(""); } } }, [isOpen, isEditing, editingCode, codes]); @@ -137,29 +129,22 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code const handleSubmit = form.handleSubmit(async (data) => { try { if (isEditing && editingCode) { - // 수정 - 부모 코드 포함 + // 수정 await updateCodeMutation.mutateAsync({ categoryCode, codeValue: editingCode.codeValue || editingCode.code_value, - data: { - ...data, - parentCodeValue: parentCodeValue || undefined, - } as UpdateCodeData, + data: data as UpdateCodeData, }); } else { - // 생성 - 부모 코드 포함 + // 생성 await createCodeMutation.mutateAsync({ categoryCode, - data: { - ...data, - parentCodeValue: parentCodeValue || undefined, - } as CreateCodeData, + data: data as CreateCodeData, }); } onClose(); form.reset(); - setParentCodeValue(""); } catch (error) { console.error("코드 저장 실패:", error); } @@ -284,43 +269,6 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code )} - {/* 부모 코드 (계층 구조) */} -
- - -

- 계층 구조가 필요한 경우 부모 코드를 선택하세요. -

-
- {/* 정렬 순서 */}
diff --git a/frontend/lib/api/commonCode.ts b/frontend/lib/api/commonCode.ts index 9784e112..c51edb11 100644 --- a/frontend/lib/api/commonCode.ts +++ b/frontend/lib/api/commonCode.ts @@ -3,8 +3,6 @@ import { CodeCategory, CodeInfo, CodeOption, - CodeHierarchyNode, - CodeChildOption, CreateCategoryRequest, UpdateCategoryRequest, CreateCodeRequest, @@ -168,34 +166,4 @@ export const commonCodeApi = { return response.data; }, }, - - // 계층 구조 API - hierarchy: { - /** - * 계층 구조 코드 조회 (트리 형태) - */ - async getTree(categoryCode: string): Promise> { - const response = await apiClient.get(`/common-codes/categories/${categoryCode}/hierarchy`); - return response.data; - }, - - /** - * 자식 코드 조회 (연쇄 선택용) - * @param categoryCode 카테고리 코드 - * @param parentCodeValue 부모 코드 값 (null이면 최상위) - */ - async getChildren( - categoryCode: string, - parentCodeValue?: string | null - ): Promise> { - const params = new URLSearchParams(); - if (parentCodeValue) { - params.append("parentCodeValue", parentCodeValue); - } - const queryString = params.toString(); - const url = `/common-codes/categories/${categoryCode}/children${queryString ? `?${queryString}` : ""}`; - const response = await apiClient.get(url); - return response.data; - }, - }, }; diff --git a/frontend/types/commonCode.ts b/frontend/types/commonCode.ts index e198ac02..046cdad7 100644 --- a/frontend/types/commonCode.ts +++ b/frontend/types/commonCode.ts @@ -25,9 +25,6 @@ export interface CodeInfo { sortOrder?: number; isActive?: string | boolean; useYn?: string; - // 계층 구조 필드 - parentCodeValue?: string | null; - depth?: number; // 기존 필드 (하위 호환성을 위해 유지) code_category?: string; @@ -36,7 +33,6 @@ export interface CodeInfo { code_name_eng?: string | null; sort_order?: number; is_active?: string; - parent_code_value?: string | null; created_date?: string | null; created_by?: string | null; updated_date?: string | null; @@ -65,9 +61,6 @@ export interface CreateCodeRequest { codeNameEng?: string; description?: string; sortOrder?: number; - // 계층 구조 필드 - parentCodeValue?: string; - depth?: number; } export interface UpdateCodeRequest { @@ -76,8 +69,6 @@ export interface UpdateCodeRequest { description?: string; sortOrder?: number; isActive?: "Y" | "N"; // 백엔드에서 기대하는 문자열 타입 - // 계층 구조 필드 - parentCodeValue?: string; } export interface CodeOption { @@ -86,24 +77,6 @@ export interface CodeOption { labelEng?: string | null; } -// 계층 구조 트리 노드 -export interface CodeHierarchyNode { - value: string; - label: string; - labelEng?: string | null; - description?: string | null; - depth: number; - parentValue?: string | null; - children: CodeHierarchyNode[]; -} - -// 자식 코드 (연쇄 선택용) -export interface CodeChildOption { - value: string; - label: string; - hasChildren: boolean; -} - export interface ReorderCodesRequest { codes: Array<{ codeValue: string;