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 userCompanyCode = req.user?.companyCode; 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), }, userCompanyCode ); 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, page, size } = req.query; const userCompanyCode = req.user?.companyCode; const result = await this.commonCodeService.getCodes( categoryCode, { search: search as string, isActive: isActive === "true" ? true : isActive === "false" ? false : undefined, page: page ? parseInt(page as string) : undefined, size: size ? parseInt(size as string) : undefined, }, userCompanyCode ); // 프론트엔드가 기대하는 형식으로 데이터 변환 const transformedData = result.data.map((code: any) => ({ // 새로운 필드명 (카멜케이스) codeValue: code.code_value, codeName: code.code_name, codeNameEng: code.code_name_eng, description: code.description, sortOrder: code.sort_order, isActive: code.is_active, useYn: code.is_active, companyCode: code.company_code, // 추가 // 기존 필드명도 유지 (하위 호환성) code_category: code.code_category, code_value: code.code_value, code_name: code.code_name, code_name_eng: code.code_name_eng, sort_order: code.sort_order, is_active: code.is_active, company_code: code.company_code, // 추가 created_date: code.created_date, created_by: code.created_by, updated_date: code.updated_date, updated_by: code.updated_by, })); return res.json({ success: true, data: transformedData, total: result.total, 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"; const companyCode = req.user?.companyCode || "*"; // 입력값 검증 if (!categoryData.categoryCode || !categoryData.categoryName) { return res.status(400).json({ success: false, message: "카테고리 코드와 이름은 필수입니다.", }); } const category = await this.commonCodeService.createCategory( categoryData, userId, companyCode ); return res.status(201).json({ success: true, data: category, message: "카테고리 생성 성공", }); } catch (error) { logger.error("카테고리 생성 실패:", error); // PostgreSQL 에러 처리 if ( (error as any)?.code === "23505" || // PostgreSQL unique_violation (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 companyCode = req.user?.companyCode; const category = await this.commonCodeService.updateCategory( categoryCode, categoryData, userId, companyCode ); 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; const companyCode = req.user?.companyCode; await this.commonCodeService.deleteCategory(categoryCode, companyCode); 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"; const companyCode = req.user?.companyCode || "*"; // 입력값 검증 if (!codeData.codeValue || !codeData.codeName) { return res.status(400).json({ success: false, message: "코드값과 코드명은 필수입니다.", }); } const code = await this.commonCodeService.createCode( categoryCode, codeData, userId, companyCode ); 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 companyCode = req.user?.companyCode; const code = await this.commonCodeService.updateCode( categoryCode, codeValue, codeData, userId, companyCode ); 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; const companyCode = req.user?.companyCode; await this.commonCodeService.deleteCode( categoryCode, codeValue, companyCode ); 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 userCompanyCode = req.user?.companyCode; const options = await this.commonCodeService.getCodeOptions( categoryCode, userCompanyCode ); 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", }); } } /** * 카테고리 중복 검사 (회사별) * GET /api/common-codes/categories/check-duplicate?field=categoryCode&value=USER_STATUS&excludeCode=OLD_CODE */ async checkCategoryDuplicate(req: AuthenticatedRequest, res: Response) { try { const { field, value, excludeCode } = req.query; const userCompanyCode = req.user?.companyCode; // 입력값 검증 if (!field || !value) { return res.status(400).json({ success: false, message: "field와 value 파라미터가 필요합니다.", }); } const validFields = ["categoryCode", "categoryName", "categoryNameEng"]; if (!validFields.includes(field as string)) { return res.status(400).json({ success: false, message: "field는 categoryCode, categoryName, categoryNameEng 중 하나여야 합니다.", }); } const result = await this.commonCodeService.checkCategoryDuplicate( field as "categoryCode" | "categoryName" | "categoryNameEng", value as string, excludeCode as string, userCompanyCode ); return res.json({ success: true, data: { ...result, field, value, }, 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/check-duplicate?field=codeValue&value=ACTIVE&excludeCode=OLD_CODE */ async checkCodeDuplicate(req: AuthenticatedRequest, res: Response) { try { const { categoryCode } = req.params; const { field, value, excludeCode } = req.query; const userCompanyCode = req.user?.companyCode; // 입력값 검증 if (!field || !value) { return res.status(400).json({ success: false, message: "field와 value 파라미터가 필요합니다.", }); } const validFields = ["codeValue", "codeName", "codeNameEng"]; if (!validFields.includes(field as string)) { return res.status(400).json({ success: false, message: "field는 codeValue, codeName, codeNameEng 중 하나여야 합니다.", }); } const result = await this.commonCodeService.checkCodeDuplicate( categoryCode, field as "codeValue" | "codeName" | "codeNameEng", value as string, excludeCode as string, userCompanyCode ); return res.json({ success: true, data: { ...result, categoryCode, field, value, }, 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", }); } } }