diff --git a/backend-node/create-common-code-tables.js b/backend-node/create-common-code-tables.js deleted file mode 100644 index f3bc6daf..00000000 --- a/backend-node/create-common-code-tables.js +++ /dev/null @@ -1,74 +0,0 @@ -const { PrismaClient } = require("@prisma/client"); -const prisma = new PrismaClient(); - -async function createCommonCodeTables() { - try { - console.log("=== 공통코드 테이블 생성 시작 ==="); - - // 1. code_category 테이블 생성 - await prisma.$executeRaw` - CREATE TABLE IF NOT EXISTS code_category ( - category_code VARCHAR(50) PRIMARY KEY, - category_name VARCHAR(100) NOT NULL, - category_name_eng VARCHAR(100), - description TEXT, - sort_order INTEGER DEFAULT 0, - is_active CHAR(1) DEFAULT 'Y', - created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - created_by VARCHAR(50), - updated_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_by VARCHAR(50) - ) - `; - console.log("✅ code_category 테이블 생성 완료"); - - // 2. code_info 테이블 생성 - await prisma.$executeRaw` - CREATE TABLE IF NOT EXISTS code_info ( - code_category VARCHAR(50) NOT NULL, - code_value VARCHAR(50) NOT NULL, - code_name VARCHAR(100) NOT NULL, - code_name_eng VARCHAR(100), - description TEXT, - sort_order INTEGER DEFAULT 0, - is_active CHAR(1) DEFAULT 'Y', - created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - created_by VARCHAR(50), - updated_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_by VARCHAR(50), - PRIMARY KEY (code_category, code_value), - CONSTRAINT fk_code_info_category - FOREIGN KEY (code_category) REFERENCES code_category(category_code) - ON DELETE CASCADE - ON UPDATE CASCADE - ) - `; - console.log("✅ code_info 테이블 생성 완료"); - - // 3. 인덱스 생성 - await prisma.$executeRaw` - CREATE INDEX IF NOT EXISTS idx_code_category_active ON code_category(is_active) - `; - await prisma.$executeRaw` - CREATE INDEX IF NOT EXISTS idx_code_category_sort ON code_category(sort_order) - `; - await prisma.$executeRaw` - CREATE INDEX IF NOT EXISTS idx_code_info_category ON code_info(code_category) - `; - await prisma.$executeRaw` - CREATE INDEX IF NOT EXISTS idx_code_info_active ON code_info(is_active) - `; - await prisma.$executeRaw` - CREATE INDEX IF NOT EXISTS idx_code_info_sort ON code_info(code_category, sort_order) - `; - console.log("✅ 인덱스 생성 완료"); - - console.log("🎉 공통코드 테이블 생성 완료!"); - } catch (error) { - console.error("❌ 오류 발생:", error); - } finally { - await prisma.$disconnect(); - } -} - -createCommonCodeTables(); diff --git a/backend-node/insert-common-code-data.js b/backend-node/insert-common-code-data.js deleted file mode 100644 index 3a6731b9..00000000 --- a/backend-node/insert-common-code-data.js +++ /dev/null @@ -1,196 +0,0 @@ -const { PrismaClient } = require("@prisma/client"); -const prisma = new PrismaClient(); - -async function insertCommonCodeData() { - try { - console.log("=== 공통코드 기본 데이터 삽입 시작 ==="); - - // 기존 데이터 삭제 (재실행 시 중복 방지) - await prisma.$executeRaw`DELETE FROM code_info WHERE code_category IN ('USER_STATUS', 'USER_TYPE', 'DEPT_TYPE', 'LANGUAGE', 'CURRENCY')`; - await prisma.$executeRaw`DELETE FROM code_category WHERE category_code IN ('USER_STATUS', 'USER_TYPE', 'DEPT_TYPE', 'LANGUAGE', 'CURRENCY')`; - console.log("✅ 기존 데이터 정리 완료"); - - // 1. 카테고리 삽입 - const categories = [ - [ - "USER_STATUS", - "사용자 상태", - "User Status", - "사용자의 활성화 상태를 나타내는 코드", - 1, - ], - [ - "USER_TYPE", - "사용자 타입", - "User Type", - "사용자의 권한 타입을 구분하는 코드", - 2, - ], - [ - "DEPT_TYPE", - "부서 타입", - "Department Type", - "부서의 분류를 나타내는 코드", - 3, - ], - [ - "LANGUAGE", - "언어 코드", - "Language Code", - "시스템에서 지원하는 언어 코드", - 4, - ], - [ - "CURRENCY", - "통화 코드", - "Currency Code", - "시스템에서 지원하는 통화 코드", - 5, - ], - ]; - - for (const [code, name, nameEng, desc, order] of categories) { - await prisma.$executeRaw` - INSERT INTO code_category (category_code, category_name, category_name_eng, description, sort_order, is_active, created_date, created_by, updated_date, updated_by) - VALUES (${code}, ${name}, ${nameEng}, ${desc}, ${order}, 'Y', now(), 'SYSTEM', now(), 'SYSTEM') - `; - } - console.log("✅ 카테고리 데이터 삽입 완료"); - - // 2. 코드 정보 삽입 - const codes = [ - // 사용자 상태 - ["USER_STATUS", "A", "활성", "Active", "정상적으로 활동 중인 사용자", 1], - [ - "USER_STATUS", - "I", - "비활성", - "Inactive", - "일시적으로 비활성화된 사용자", - 2, - ], - [ - "USER_STATUS", - "S", - "휴면", - "Sleep", - "장기간 미접속으로 휴면 상태인 사용자", - 3, - ], - ["USER_STATUS", "D", "삭제", "Deleted", "삭제 처리된 사용자", 4], - - // 사용자 타입 - [ - "USER_TYPE", - "ADMIN", - "관리자", - "Administrator", - "시스템 전체를 관리할 수 있는 최고 권한", - 1, - ], - [ - "USER_TYPE", - "USER", - "일반사용자", - "User", - "일반적인 업무 기능을 사용할 수 있는 사용자", - 2, - ], - [ - "USER_TYPE", - "GUEST", - "게스트", - "Guest", - "제한적인 기능만 사용할 수 있는 게스트 사용자", - 3, - ], - [ - "USER_TYPE", - "PARTNER", - "협력업체", - "Partner", - "협력업체 직원으로 특정 기능만 사용 가능", - 4, - ], - - // 부서 타입 - [ - "DEPT_TYPE", - "SALES", - "영업부", - "Sales Department", - "영업 관련 업무를 담당하는 부서", - 1, - ], - [ - "DEPT_TYPE", - "DEV", - "개발부", - "Development Department", - "시스템 개발을 담당하는 부서", - 2, - ], - [ - "DEPT_TYPE", - "HR", - "인사부", - "Human Resources Department", - "인사 관리를 담당하는 부서", - 3, - ], - [ - "DEPT_TYPE", - "FINANCE", - "재무부", - "Finance Department", - "재무 관리를 담당하는 부서", - 4, - ], - [ - "DEPT_TYPE", - "ADMIN", - "관리부", - "Administration Department", - "일반 관리 업무를 담당하는 부서", - 5, - ], - - // 언어 코드 - ["LANGUAGE", "KR", "한국어", "Korean", "한국어 언어 설정", 1], - ["LANGUAGE", "US", "영어", "English", "영어 언어 설정", 2], - ["LANGUAGE", "CN", "중국어", "Chinese", "중국어 언어 설정", 3], - ["LANGUAGE", "JP", "일본어", "Japanese", "일본어 언어 설정", 4], - - // 통화 코드 - ["CURRENCY", "KRW", "원", "Korean Won", "대한민국 원화", 1], - ["CURRENCY", "USD", "달러", "US Dollar", "미국 달러", 2], - ["CURRENCY", "EUR", "유로", "Euro", "유럽 유로", 3], - ["CURRENCY", "JPY", "엔", "Japanese Yen", "일본 엔화", 4], - ["CURRENCY", "CNY", "위안", "Chinese Yuan", "중국 위안화", 5], - ]; - - for (const [category, value, name, nameEng, desc, order] of codes) { - await prisma.$executeRaw` - INSERT INTO code_info (code_category, code_value, code_name, code_name_eng, description, sort_order, is_active, created_date, created_by, updated_date, updated_by) - VALUES (${category}, ${value}, ${name}, ${nameEng}, ${desc}, ${order}, 'Y', now(), 'SYSTEM', now(), 'SYSTEM') - `; - } - console.log("✅ 코드 정보 데이터 삽입 완료"); - - // 3. 삽입 결과 확인 - const categoryCount = - await prisma.$queryRaw`SELECT COUNT(*) as count FROM code_category WHERE created_by = 'SYSTEM'`; - const codeCount = - await prisma.$queryRaw`SELECT COUNT(*) as count FROM code_info WHERE created_by = 'SYSTEM'`; - - console.log(`📊 삽입된 카테고리 수: ${categoryCount[0].count}`); - console.log(`📊 삽입된 코드 수: ${codeCount[0].count}`); - console.log("🎉 공통코드 기본 데이터 삽입 완료!"); - } catch (error) { - console.error("❌ 오류 발생:", error); - } finally { - await prisma.$disconnect(); - } -} - -insertCommonCodeData(); diff --git a/backend-node/src/app.ts b/backend-node/src/app.ts index df6cf3bd..3b281d19 100644 --- a/backend-node/src/app.ts +++ b/backend-node/src/app.ts @@ -14,6 +14,7 @@ import adminRoutes from "./routes/adminRoutes"; import multilangRoutes from "./routes/multilangRoutes"; import tableManagementRoutes from "./routes/tableManagementRoutes"; import screenManagementRoutes from "./routes/screenManagementRoutes"; +import commonCodeRoutes from "./routes/commonCodeRoutes"; // import userRoutes from './routes/userRoutes'; // import menuRoutes from './routes/menuRoutes'; @@ -65,6 +66,7 @@ app.use("/api/admin", adminRoutes); app.use("/api/multilang", multilangRoutes); app.use("/api/table-management", tableManagementRoutes); app.use("/api/screen-management", screenManagementRoutes); +app.use("/api/common-codes", commonCodeRoutes); // app.use('/api/users', userRoutes); // app.use('/api/menus', menuRoutes); diff --git a/backend-node/src/controllers/commonCodeController.ts b/backend-node/src/controllers/commonCodeController.ts new file mode 100644 index 00000000..d38251b6 --- /dev/null +++ b/backend-node/src/controllers/commonCodeController.ts @@ -0,0 +1,398 @@ +import { Request, Response } from "express"; +import { + CommonCodeService, + CreateCategoryData, + CreateCodeData, +} from "../services/commonCodeService"; +import { AuthenticatedRequest } from "../types/auth"; +import { logger } from "../utils/logger"; + +export class CommonCodeController { + private commonCodeService: CommonCodeService; + + constructor() { + this.commonCodeService = new CommonCodeService(); + } + + /** + * 카테고리 목록 조회 + * GET /api/common-codes/categories + */ + async getCategories(req: AuthenticatedRequest, res: Response) { + try { + const { search, isActive, page = "1", size = "20" } = req.query; + + const categories = await this.commonCodeService.getCategories({ + search: search as string, + isActive: + isActive === "true" ? true : isActive === "false" ? false : undefined, + page: parseInt(page as string), + size: parseInt(size as string), + }); + + return res.json({ + success: true, + data: categories.data, + total: categories.total, + message: "카테고리 목록 조회 성공", + }); + } catch (error) { + logger.error("카테고리 목록 조회 실패:", error); + return res.status(500).json({ + success: false, + message: "카테고리 목록 조회 중 오류가 발생했습니다.", + error: error instanceof Error ? error.message : "Unknown error", + }); + } + } + + /** + * 카테고리별 코드 목록 조회 + * GET /api/common-codes/categories/:categoryCode/codes + */ + async getCodes(req: AuthenticatedRequest, res: Response) { + try { + const { categoryCode } = req.params; + const { search, isActive } = req.query; + + const codes = await this.commonCodeService.getCodes(categoryCode, { + search: search as string, + isActive: + isActive === "true" ? true : isActive === "false" ? false : undefined, + }); + + return res.json({ + success: true, + data: codes, + 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", + }); + } + } + + /** + * 카테고리 생성 + * POST /api/common-codes/categories + */ + async createCategory(req: AuthenticatedRequest, res: Response) { + try { + const categoryData: CreateCategoryData = req.body; + const userId = req.user?.userId || "SYSTEM"; // 인증 미들웨어에서 설정된 사용자 ID + + // 입력값 검증 + if (!categoryData.categoryCode || !categoryData.categoryName) { + return res.status(400).json({ + success: false, + message: "카테고리 코드와 이름은 필수입니다.", + }); + } + + const category = await this.commonCodeService.createCategory( + categoryData, + userId + ); + + return res.status(201).json({ + success: true, + data: category, + message: "카테고리 생성 성공", + }); + } catch (error) { + logger.error("카테고리 생성 실패:", error); + + // Prisma 에러 처리 + if ( + error instanceof Error && + error.message.includes("Unique constraint") + ) { + return res.status(409).json({ + success: false, + message: "이미 존재하는 카테고리 코드입니다.", + }); + } + + return res.status(500).json({ + success: false, + message: "카테고리 생성 중 오류가 발생했습니다.", + error: error instanceof Error ? error.message : "Unknown error", + }); + } + } + + /** + * 카테고리 수정 + * PUT /api/common-codes/categories/:categoryCode + */ + async updateCategory(req: AuthenticatedRequest, res: Response) { + try { + const { categoryCode } = req.params; + const categoryData: Partial = req.body; + const userId = req.user?.userId || "SYSTEM"; + + const category = await this.commonCodeService.updateCategory( + categoryCode, + categoryData, + userId + ); + + return res.json({ + success: true, + data: category, + message: "카테고리 수정 성공", + }); + } catch (error) { + logger.error(`카테고리 수정 실패 (${req.params.categoryCode}):`, error); + + if ( + error instanceof Error && + error.message.includes("Record to update not found") + ) { + return res.status(404).json({ + success: false, + message: "존재하지 않는 카테고리입니다.", + }); + } + + return res.status(500).json({ + success: false, + message: "카테고리 수정 중 오류가 발생했습니다.", + error: error instanceof Error ? error.message : "Unknown error", + }); + } + } + + /** + * 카테고리 삭제 + * DELETE /api/common-codes/categories/:categoryCode + */ + async deleteCategory(req: AuthenticatedRequest, res: Response) { + try { + const { categoryCode } = req.params; + + await this.commonCodeService.deleteCategory(categoryCode); + + return res.json({ + success: true, + message: "카테고리 삭제 성공", + }); + } catch (error) { + logger.error(`카테고리 삭제 실패 (${req.params.categoryCode}):`, error); + + if ( + error instanceof Error && + error.message.includes("Record to delete does not exist") + ) { + return res.status(404).json({ + success: false, + message: "존재하지 않는 카테고리입니다.", + }); + } + + return res.status(500).json({ + success: false, + message: "카테고리 삭제 중 오류가 발생했습니다.", + error: error instanceof Error ? error.message : "Unknown error", + }); + } + } + + /** + * 코드 생성 + * POST /api/common-codes/categories/:categoryCode/codes + */ + async createCode(req: AuthenticatedRequest, res: Response) { + try { + const { categoryCode } = req.params; + const codeData: CreateCodeData = req.body; + const userId = req.user?.userId || "SYSTEM"; + + // 입력값 검증 + if (!codeData.codeValue || !codeData.codeName) { + return res.status(400).json({ + success: false, + message: "코드값과 코드명은 필수입니다.", + }); + } + + const code = await this.commonCodeService.createCode( + categoryCode, + codeData, + userId + ); + + return res.status(201).json({ + success: true, + data: code, + message: "코드 생성 성공", + }); + } catch (error) { + logger.error(`코드 생성 실패 (${req.params.categoryCode}):`, error); + + if ( + error instanceof Error && + error.message.includes("Unique constraint") + ) { + return res.status(409).json({ + success: false, + message: "이미 존재하는 코드값입니다.", + }); + } + + return res.status(500).json({ + success: false, + message: "코드 생성 중 오류가 발생했습니다.", + error: error instanceof Error ? error.message : "Unknown error", + }); + } + } + + /** + * 코드 수정 + * PUT /api/common-codes/categories/:categoryCode/codes/:codeValue + */ + async updateCode(req: AuthenticatedRequest, res: Response) { + try { + const { categoryCode, codeValue } = req.params; + const codeData: Partial = req.body; + const userId = req.user?.userId || "SYSTEM"; + + const code = await this.commonCodeService.updateCode( + categoryCode, + codeValue, + codeData, + userId + ); + + return res.json({ + success: true, + data: code, + message: "코드 수정 성공", + }); + } catch (error) { + logger.error( + `코드 수정 실패 (${req.params.categoryCode}.${req.params.codeValue}):`, + error + ); + + if ( + error instanceof Error && + error.message.includes("Record to update not found") + ) { + return res.status(404).json({ + success: false, + message: "존재하지 않는 코드입니다.", + }); + } + + return res.status(500).json({ + success: false, + message: "코드 수정 중 오류가 발생했습니다.", + error: error instanceof Error ? error.message : "Unknown error", + }); + } + } + + /** + * 코드 삭제 + * DELETE /api/common-codes/categories/:categoryCode/codes/:codeValue + */ + async deleteCode(req: AuthenticatedRequest, res: Response) { + try { + const { categoryCode, codeValue } = req.params; + + await this.commonCodeService.deleteCode(categoryCode, codeValue); + + return res.json({ + success: true, + message: "코드 삭제 성공", + }); + } catch (error) { + logger.error( + `코드 삭제 실패 (${req.params.categoryCode}.${req.params.codeValue}):`, + error + ); + + if ( + error instanceof Error && + error.message.includes("Record to delete does not exist") + ) { + return res.status(404).json({ + success: false, + message: "존재하지 않는 코드입니다.", + }); + } + + return res.status(500).json({ + success: false, + message: "코드 삭제 중 오류가 발생했습니다.", + error: error instanceof Error ? error.message : "Unknown error", + }); + } + } + + /** + * 카테고리별 옵션 조회 (화면관리용) + * GET /api/common-codes/categories/:categoryCode/options + */ + async getCodeOptions(req: AuthenticatedRequest, res: Response) { + try { + const { categoryCode } = req.params; + + const options = await this.commonCodeService.getCodeOptions(categoryCode); + + return res.json({ + success: true, + data: options, + 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", + }); + } + } + + /** + * 코드 순서 변경 + * PUT /api/common-codes/categories/:categoryCode/codes/reorder + */ + async reorderCodes(req: AuthenticatedRequest, res: Response) { + try { + const { categoryCode } = req.params; + const { codes } = req.body as { + codes: Array<{ codeValue: string; sortOrder: number }>; + }; + const userId = req.user?.userId || "SYSTEM"; + + if (!codes || !Array.isArray(codes)) { + return res.status(400).json({ + success: false, + message: "코드 순서 정보가 올바르지 않습니다.", + }); + } + + await this.commonCodeService.reorderCodes(categoryCode, codes, userId); + + return res.json({ + success: true, + message: "코드 순서 변경 성공", + }); + } 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/routes/commonCodeRoutes.ts b/backend-node/src/routes/commonCodeRoutes.ts new file mode 100644 index 00000000..995650f8 --- /dev/null +++ b/backend-node/src/routes/commonCodeRoutes.ts @@ -0,0 +1,49 @@ +import { Router } from "express"; +import { CommonCodeController } from "../controllers/commonCodeController"; +import { authenticateToken } from "../middleware/authMiddleware"; + +const router = Router(); +const commonCodeController = new CommonCodeController(); + +// 모든 공통코드 API는 인증이 필요 +router.use(authenticateToken); + +// 카테고리 관련 라우트 +router.get("/categories", (req, res) => + commonCodeController.getCategories(req, res) +); +router.post("/categories", (req, res) => + commonCodeController.createCategory(req, res) +); +router.put("/categories/:categoryCode", (req, res) => + commonCodeController.updateCategory(req, res) +); +router.delete("/categories/:categoryCode", (req, res) => + commonCodeController.deleteCategory(req, res) +); + +// 코드 관련 라우트 +router.get("/categories/:categoryCode/codes", (req, res) => + commonCodeController.getCodes(req, res) +); +router.post("/categories/:categoryCode/codes", (req, res) => + commonCodeController.createCode(req, res) +); +router.put("/categories/:categoryCode/codes/:codeValue", (req, res) => + commonCodeController.updateCode(req, res) +); +router.delete("/categories/:categoryCode/codes/:codeValue", (req, res) => + commonCodeController.deleteCode(req, res) +); + +// 코드 순서 변경 +router.put("/categories/:categoryCode/codes/reorder", (req, res) => + commonCodeController.reorderCodes(req, res) +); + +// 화면관리용 옵션 조회 +router.get("/categories/:categoryCode/options", (req, res) => + commonCodeController.getCodeOptions(req, res) +); + +export default router; diff --git a/backend-node/src/services/commonCodeService.ts b/backend-node/src/services/commonCodeService.ts new file mode 100644 index 00000000..ce674a70 --- /dev/null +++ b/backend-node/src/services/commonCodeService.ts @@ -0,0 +1,370 @@ +import { PrismaClient } from "@prisma/client"; +import { logger } from "../utils/logger"; + +const prisma = new PrismaClient(); + +export interface CodeCategory { + category_code: string; + category_name: string; + category_name_eng?: string | null; + description?: string | null; + sort_order: number; + is_active: string; + created_date?: Date | null; + created_by?: string | null; + updated_date?: Date | null; + updated_by?: string | null; +} + +export interface CodeInfo { + code_category: string; + code_value: string; + code_name: string; + code_name_eng?: string | null; + description?: string | null; + sort_order: number; + is_active: string; + created_date?: Date | null; + created_by?: string | null; + updated_date?: Date | null; + updated_by?: string | null; +} + +export interface GetCategoriesParams { + search?: string; + isActive?: boolean; + page?: number; + size?: number; +} + +export interface GetCodesParams { + search?: string; + isActive?: boolean; +} + +export interface CreateCategoryData { + categoryCode: string; + categoryName: string; + categoryNameEng?: string; + description?: string; + sortOrder?: number; +} + +export interface CreateCodeData { + codeValue: string; + codeName: string; + codeNameEng?: string; + description?: string; + sortOrder?: number; +} + +export class CommonCodeService { + /** + * 카테고리 목록 조회 + */ + async getCategories(params: GetCategoriesParams) { + try { + const { search, isActive, page = 1, size = 20 } = params; + + let whereClause: any = {}; + + if (search) { + whereClause.OR = [ + { category_name: { contains: search, mode: "insensitive" } }, + { category_code: { contains: search, mode: "insensitive" } }, + ]; + } + + if (isActive !== undefined) { + whereClause.is_active = isActive ? "Y" : "N"; + } + + const offset = (page - 1) * size; + + const [categories, total] = await Promise.all([ + prisma.code_category.findMany({ + where: whereClause, + orderBy: [{ sort_order: "asc" }, { category_code: "asc" }], + skip: offset, + take: size, + }), + prisma.code_category.count({ where: whereClause }), + ]); + + logger.info( + `카테고리 조회 완료: ${categories.length}개, 전체: ${total}개` + ); + + return { + data: categories, + total, + }; + } catch (error) { + logger.error("카테고리 조회 중 오류:", error); + throw error; + } + } + + /** + * 카테고리별 코드 목록 조회 + */ + async getCodes(categoryCode: string, params: GetCodesParams) { + try { + const { search, isActive } = params; + + let whereClause: any = { + code_category: categoryCode, + }; + + if (search) { + whereClause.OR = [ + { code_name: { contains: search, mode: "insensitive" } }, + { code_value: { contains: search, mode: "insensitive" } }, + ]; + } + + if (isActive !== undefined) { + whereClause.is_active = isActive ? "Y" : "N"; + } + + const codes = await prisma.code_info.findMany({ + where: whereClause, + orderBy: [{ sort_order: "asc" }, { code_value: "asc" }], + }); + + logger.info(`코드 조회 완료: ${categoryCode} - ${codes.length}개`); + + return codes; + } catch (error) { + logger.error(`코드 조회 중 오류 (${categoryCode}):`, error); + throw error; + } + } + + /** + * 카테고리 생성 + */ + async createCategory(data: CreateCategoryData, createdBy: string) { + try { + const category = await prisma.code_category.create({ + data: { + category_code: data.categoryCode, + category_name: data.categoryName, + category_name_eng: data.categoryNameEng, + description: data.description, + sort_order: data.sortOrder || 0, + is_active: "Y", + created_by: createdBy, + updated_by: createdBy, + }, + }); + + logger.info(`카테고리 생성 완료: ${data.categoryCode}`); + return category; + } catch (error) { + logger.error("카테고리 생성 중 오류:", error); + throw error; + } + } + + /** + * 카테고리 수정 + */ + async updateCategory( + categoryCode: string, + data: Partial, + updatedBy: string + ) { + try { + const category = await prisma.code_category.update({ + where: { category_code: categoryCode }, + data: { + category_name: data.categoryName, + category_name_eng: data.categoryNameEng, + description: data.description, + sort_order: data.sortOrder, + updated_by: updatedBy, + updated_date: new Date(), + }, + }); + + logger.info(`카테고리 수정 완료: ${categoryCode}`); + return category; + } catch (error) { + logger.error(`카테고리 수정 중 오류 (${categoryCode}):`, error); + throw error; + } + } + + /** + * 카테고리 삭제 + */ + async deleteCategory(categoryCode: string) { + try { + await prisma.code_category.delete({ + where: { category_code: categoryCode }, + }); + + logger.info(`카테고리 삭제 완료: ${categoryCode}`); + } catch (error) { + logger.error(`카테고리 삭제 중 오류 (${categoryCode}):`, error); + throw error; + } + } + + /** + * 코드 생성 + */ + async createCode( + categoryCode: string, + data: CreateCodeData, + createdBy: string + ) { + try { + const code = await prisma.code_info.create({ + data: { + code_category: categoryCode, + code_value: data.codeValue, + code_name: data.codeName, + code_name_eng: data.codeNameEng, + description: data.description, + sort_order: data.sortOrder || 0, + is_active: "Y", + created_by: createdBy, + updated_by: createdBy, + }, + }); + + logger.info(`코드 생성 완료: ${categoryCode}.${data.codeValue}`); + return code; + } catch (error) { + logger.error( + `코드 생성 중 오류 (${categoryCode}.${data.codeValue}):`, + error + ); + throw error; + } + } + + /** + * 코드 수정 + */ + async updateCode( + categoryCode: string, + codeValue: string, + data: Partial, + updatedBy: string + ) { + try { + const code = await prisma.code_info.update({ + where: { + code_category_code_value: { + code_category: categoryCode, + code_value: codeValue, + }, + }, + data: { + code_name: data.codeName, + code_name_eng: data.codeNameEng, + description: data.description, + sort_order: data.sortOrder, + updated_by: updatedBy, + updated_date: new Date(), + }, + }); + + logger.info(`코드 수정 완료: ${categoryCode}.${codeValue}`); + return code; + } catch (error) { + logger.error(`코드 수정 중 오류 (${categoryCode}.${codeValue}):`, error); + throw error; + } + } + + /** + * 코드 삭제 + */ + async deleteCode(categoryCode: string, codeValue: string) { + try { + await prisma.code_info.delete({ + where: { + code_category_code_value: { + code_category: categoryCode, + code_value: codeValue, + }, + }, + }); + + logger.info(`코드 삭제 완료: ${categoryCode}.${codeValue}`); + } catch (error) { + logger.error(`코드 삭제 중 오류 (${categoryCode}.${codeValue}):`, error); + throw error; + } + } + + /** + * 카테고리별 옵션 조회 (화면관리용) + */ + async getCodeOptions(categoryCode: string) { + try { + const codes = await prisma.code_info.findMany({ + where: { + code_category: categoryCode, + is_active: "Y", + }, + select: { + code_value: true, + code_name: true, + code_name_eng: true, + sort_order: true, + }, + orderBy: [{ sort_order: "asc" }, { code_value: "asc" }], + }); + + const options = codes.map((code) => ({ + value: code.code_value, + label: code.code_name, + labelEng: code.code_name_eng, + })); + + logger.info(`코드 옵션 조회 완료: ${categoryCode} - ${options.length}개`); + return options; + } catch (error) { + logger.error(`코드 옵션 조회 중 오류 (${categoryCode}):`, error); + throw error; + } + } + + /** + * 코드 순서 변경 + */ + async reorderCodes( + categoryCode: string, + codes: Array<{ codeValue: string; sortOrder: number }>, + updatedBy: string + ) { + try { + const updatePromises = codes.map(({ codeValue, sortOrder }) => + prisma.code_info.update({ + where: { + code_category_code_value: { + code_category: categoryCode, + code_value: codeValue, + }, + }, + data: { + sort_order: sortOrder, + updated_by: updatedBy, + updated_date: new Date(), + }, + }) + ); + + await Promise.all(updatePromises); + logger.info(`코드 순서 변경 완료: ${categoryCode} - ${codes.length}개`); + } catch (error) { + logger.error(`코드 순서 변경 중 오류 (${categoryCode}):`, error); + throw error; + } + } +} diff --git a/backend-node/src/types/commonCode.ts b/backend-node/src/types/commonCode.ts new file mode 100644 index 00000000..d68022e5 --- /dev/null +++ b/backend-node/src/types/commonCode.ts @@ -0,0 +1,93 @@ +// 공통코드 관련 타입 정의 + +export interface CodeCategory { + category_code: string; + category_name: string; + category_name_eng?: string | null; + description?: string | null; + sort_order: number; + is_active: string; + created_date?: Date | null; + created_by?: string | null; + updated_date?: Date | null; + updated_by?: string | null; +} + +export interface CodeInfo { + code_category: string; + code_value: string; + code_name: string; + code_name_eng?: string | null; + description?: string | null; + sort_order: number; + is_active: string; + created_date?: Date | null; + created_by?: string | null; + updated_date?: Date | null; + updated_by?: string | null; +} + +export interface CreateCategoryRequest { + categoryCode: string; + categoryName: string; + categoryNameEng?: string; + description?: string; + sortOrder?: number; +} + +export interface UpdateCategoryRequest { + categoryName?: string; + categoryNameEng?: string; + description?: string; + sortOrder?: number; + isActive?: boolean; +} + +export interface CreateCodeRequest { + codeValue: string; + codeName: string; + codeNameEng?: string; + description?: string; + sortOrder?: number; +} + +export interface UpdateCodeRequest { + codeName?: string; + codeNameEng?: string; + description?: string; + sortOrder?: number; + isActive?: boolean; +} + +export interface CodeOption { + value: string; + label: string; + labelEng?: string | null; +} + +export interface ReorderCodesRequest { + codes: Array<{ + codeValue: string; + sortOrder: number; + }>; +} + +export interface GetCategoriesQuery { + search?: string; + isActive?: string; + page?: string; + size?: string; +} + +export interface GetCodesQuery { + search?: string; + isActive?: string; +} + +export interface ApiResponse { + success: boolean; + data?: T; + message: string; + error?: string; + total?: number; +} diff --git a/docs/공통코드_관리_시스템_설계.md b/docs/공통코드_관리_시스템_설계.md index 440553e1..3c51cd1e 100644 --- a/docs/공통코드_관리_시스템_설계.md +++ b/docs/공통코드_관리_시스템_설계.md @@ -650,7 +650,7 @@ export class CommonCodeService { ## 📅 개발 계획 및 진행상황 -### ✅ Phase 1: 기본 구조 및 데이터베이스 (완료) +### ✅ Phase 1: (완료) - [x] 데이터베이스 스키마 설계 및 생성 - [x] Prisma 스키마에 공통코드 모델 추가 @@ -663,15 +663,21 @@ export class CommonCodeService { - Docker 컨테이너에서 테이블 생성 스크립트 실행 - 5개 카테고리, 22개 기본 코드 데이터 삽입 완료 -### ⏳ Phase 2: 백엔드 API 구현 (예정) +### ✅ Phase 2: 백엔드 API 구현 (완료) -- [ ] CommonCodeController 구현 -- [ ] CommonCodeService 구현 -- [ ] 카테고리 CRUD API 구현 -- [ ] 코드 상세 CRUD API 구현 -- [ ] 정렬 순서 변경 API 구현 +- [x] CommonCodeController 구현 +- [x] CommonCodeService 구현 +- [x] 카테고리 CRUD API 구현 +- [x] 코드 상세 CRUD API 구현 +- [x] 정렬 순서 변경 API 구현 -**목표 기간**: 2일 +**완료 내용:** + +- 모든 CRUD API 정상 작동 확인 +- JWT 인증 연동 완료 +- 검색, 정렬, 페이징 기능 구현 +- Prisma ORM 연동 완료 +- TypeScript 타입 정의 완료 ### ⏳ Phase 3: 프론트엔드 기본 구현 (예정)