백엔드 API 구현 완료

This commit is contained in:
hyeonsu 2025-09-02 11:30:19 +09:00
parent 3129e3663f
commit d1b6656d58
8 changed files with 926 additions and 278 deletions

View File

@ -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();

View File

@ -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();

View File

@ -14,6 +14,7 @@ import adminRoutes from "./routes/adminRoutes";
import multilangRoutes from "./routes/multilangRoutes"; import multilangRoutes from "./routes/multilangRoutes";
import tableManagementRoutes from "./routes/tableManagementRoutes"; import tableManagementRoutes from "./routes/tableManagementRoutes";
import screenManagementRoutes from "./routes/screenManagementRoutes"; import screenManagementRoutes from "./routes/screenManagementRoutes";
import commonCodeRoutes from "./routes/commonCodeRoutes";
// import userRoutes from './routes/userRoutes'; // import userRoutes from './routes/userRoutes';
// import menuRoutes from './routes/menuRoutes'; // import menuRoutes from './routes/menuRoutes';
@ -65,6 +66,7 @@ app.use("/api/admin", adminRoutes);
app.use("/api/multilang", multilangRoutes); app.use("/api/multilang", multilangRoutes);
app.use("/api/table-management", tableManagementRoutes); app.use("/api/table-management", tableManagementRoutes);
app.use("/api/screen-management", screenManagementRoutes); app.use("/api/screen-management", screenManagementRoutes);
app.use("/api/common-codes", commonCodeRoutes);
// app.use('/api/users', userRoutes); // app.use('/api/users', userRoutes);
// app.use('/api/menus', menuRoutes); // app.use('/api/menus', menuRoutes);

View File

@ -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<CreateCategoryData> = 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<CreateCodeData> = 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",
});
}
}
}

View File

@ -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;

View File

@ -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<CreateCategoryData>,
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<CreateCodeData>,
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;
}
}
}

View File

@ -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<T = any> {
success: boolean;
data?: T;
message: string;
error?: string;
total?: number;
}

View File

@ -650,7 +650,7 @@ export class CommonCodeService {
## 📅 개발 계획 및 진행상황 ## 📅 개발 계획 및 진행상황
### ✅ Phase 1: 기본 구조 및 데이터베이스 (완료) ### ✅ Phase 1: (완료)
- [x] 데이터베이스 스키마 설계 및 생성 - [x] 데이터베이스 스키마 설계 및 생성
- [x] Prisma 스키마에 공통코드 모델 추가 - [x] Prisma 스키마에 공통코드 모델 추가
@ -663,15 +663,21 @@ export class CommonCodeService {
- Docker 컨테이너에서 테이블 생성 스크립트 실행 - Docker 컨테이너에서 테이블 생성 스크립트 실행
- 5개 카테고리, 22개 기본 코드 데이터 삽입 완료 - 5개 카테고리, 22개 기본 코드 데이터 삽입 완료
### ⏳ Phase 2: 백엔드 API 구현 (예정) ### ✅ Phase 2: 백엔드 API 구현 (완료)
- [ ] CommonCodeController 구현 - [x] CommonCodeController 구현
- [ ] CommonCodeService 구현 - [x] CommonCodeService 구현
- [ ] 카테고리 CRUD API 구현 - [x] 카테고리 CRUD API 구현
- [ ] 코드 상세 CRUD API 구현 - [x] 코드 상세 CRUD API 구현
- [ ] 정렬 순서 변경 API 구현 - [x] 정렬 순서 변경 API 구현
**목표 기간**: 2일 **완료 내용:**
- 모든 CRUD API 정상 작동 확인
- JWT 인증 연동 완료
- 검색, 정렬, 페이징 기능 구현
- Prisma ORM 연동 완료
- TypeScript 타입 정의 완료
### ⏳ Phase 3: 프론트엔드 기본 구현 (예정) ### ⏳ Phase 3: 프론트엔드 기본 구현 (예정)