diff --git a/backend-node/src/services/commonCodeService.ts b/backend-node/src/services/commonCodeService.ts index 48f24cda..f37310ae 100644 --- a/backend-node/src/services/commonCodeService.ts +++ b/backend-node/src/services/commonCodeService.ts @@ -1,5 +1,4 @@ -import { PrismaClient } from "@prisma/client"; -import prisma from "../config/database"; +import { query, queryOne, transaction } from "../database/db"; import { logger } from "../utils/logger"; export interface CodeCategory { @@ -69,30 +68,46 @@ export class CommonCodeService { try { const { search, isActive, page = 1, size = 20 } = params; - let whereClause: any = {}; + const whereConditions: string[] = []; + const values: any[] = []; + let paramIndex = 1; if (search) { - whereClause.OR = [ - { category_name: { contains: search, mode: "insensitive" } }, - { category_code: { contains: search, mode: "insensitive" } }, - ]; + whereConditions.push( + `(category_name ILIKE $${paramIndex} OR category_code ILIKE $${paramIndex})` + ); + values.push(`%${search}%`); + paramIndex++; } if (isActive !== undefined) { - whereClause.is_active = isActive ? "Y" : "N"; + whereConditions.push(`is_active = $${paramIndex++}`); + values.push(isActive ? "Y" : "N"); } + const whereClause = + whereConditions.length > 0 + ? `WHERE ${whereConditions.join(" AND ")}` + : ""; + 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 }), - ]); + // 카테고리 조회 + const categories = await query( + `SELECT * FROM code_category + ${whereClause} + ORDER BY sort_order ASC, category_code ASC + LIMIT $${paramIndex++} OFFSET $${paramIndex++}`, + [...values, size, offset] + ); + + // 전체 개수 조회 + const countResult = await queryOne<{ count: string }>( + `SELECT COUNT(*) as count FROM code_category ${whereClause}`, + values + ); + + const total = parseInt(countResult?.count || "0"); logger.info( `카테고리 조회 완료: ${categories.length}개, 전체: ${total}개` @@ -115,32 +130,43 @@ export class CommonCodeService { try { const { search, isActive, page = 1, size = 20 } = params; - let whereClause: any = { - code_category: categoryCode, - }; + const whereConditions: string[] = ["code_category = $1"]; + const values: any[] = [categoryCode]; + let paramIndex = 2; if (search) { - whereClause.OR = [ - { code_name: { contains: search, mode: "insensitive" } }, - { code_value: { contains: search, mode: "insensitive" } }, - ]; + whereConditions.push( + `(code_name ILIKE $${paramIndex} OR code_value ILIKE $${paramIndex})` + ); + values.push(`%${search}%`); + paramIndex++; } if (isActive !== undefined) { - whereClause.is_active = isActive ? "Y" : "N"; + whereConditions.push(`is_active = $${paramIndex++}`); + values.push(isActive ? "Y" : "N"); } + const whereClause = `WHERE ${whereConditions.join(" AND ")}`; + const offset = (page - 1) * size; - const [codes, total] = await Promise.all([ - prisma.code_info.findMany({ - where: whereClause, - orderBy: [{ sort_order: "asc" }, { code_value: "asc" }], - skip: offset, - take: size, - }), - prisma.code_info.count({ where: whereClause }), - ]); + // 코드 조회 + const codes = await query( + `SELECT * FROM code_info + ${whereClause} + ORDER BY sort_order ASC, code_value ASC + LIMIT $${paramIndex++} OFFSET $${paramIndex++}`, + [...values, size, offset] + ); + + // 전체 개수 조회 + const countResult = await queryOne<{ count: string }>( + `SELECT COUNT(*) as count FROM code_info ${whereClause}`, + values + ); + + const total = parseInt(countResult?.count || "0"); logger.info( `코드 조회 완료: ${categoryCode} - ${codes.length}개, 전체: ${total}개` @@ -158,18 +184,22 @@ export class CommonCodeService { */ 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, - }, - }); + const category = await queryOne( + `INSERT INTO code_category + (category_code, category_name, category_name_eng, description, sort_order, + is_active, created_by, updated_by, created_date, updated_date) + VALUES ($1, $2, $3, $4, $5, 'Y', $6, $7, NOW(), NOW()) + RETURNING *`, + [ + data.categoryCode, + data.categoryName, + data.categoryNameEng || null, + data.description || null, + data.sortOrder || 0, + createdBy, + createdBy, + ] + ); logger.info(`카테고리 생성 완료: ${data.categoryCode}`); return category; @@ -190,23 +220,46 @@ export class CommonCodeService { try { // 디버깅: 받은 데이터 로그 logger.info(`카테고리 수정 데이터:`, { categoryCode, data }); - 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, - is_active: - typeof data.isActive === "boolean" - ? data.isActive - ? "Y" - : "N" - : data.isActive, // boolean이면 "Y"/"N"으로 변환 - updated_by: updatedBy, - updated_date: new Date(), - }, - }); + + // 동적 UPDATE 쿼리 생성 + const updateFields: string[] = ["updated_by = $1", "updated_date = NOW()"]; + const values: any[] = [updatedBy]; + let paramIndex = 2; + + if (data.categoryName !== undefined) { + updateFields.push(`category_name = $${paramIndex++}`); + values.push(data.categoryName); + } + if (data.categoryNameEng !== undefined) { + updateFields.push(`category_name_eng = $${paramIndex++}`); + values.push(data.categoryNameEng); + } + if (data.description !== undefined) { + updateFields.push(`description = $${paramIndex++}`); + values.push(data.description); + } + if (data.sortOrder !== undefined) { + updateFields.push(`sort_order = $${paramIndex++}`); + values.push(data.sortOrder); + } + if (data.isActive !== undefined) { + const activeValue = + typeof data.isActive === "boolean" + ? data.isActive + ? "Y" + : "N" + : data.isActive; + updateFields.push(`is_active = $${paramIndex++}`); + values.push(activeValue); + } + + const category = await queryOne( + `UPDATE code_category + SET ${updateFields.join(", ")} + WHERE category_code = $${paramIndex} + RETURNING *`, + [...values, categoryCode] + ); logger.info(`카테고리 수정 완료: ${categoryCode}`); return category; @@ -221,9 +274,9 @@ export class CommonCodeService { */ async deleteCategory(categoryCode: string) { try { - await prisma.code_category.delete({ - where: { category_code: categoryCode }, - }); + await query(`DELETE FROM code_category WHERE category_code = $1`, [ + categoryCode, + ]); logger.info(`카테고리 삭제 완료: ${categoryCode}`); } catch (error) { @@ -241,19 +294,23 @@ export class CommonCodeService { 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, - }, - }); + const code = await queryOne( + `INSERT INTO code_info + (code_category, code_value, code_name, code_name_eng, description, sort_order, + is_active, created_by, updated_by, created_date, updated_date) + VALUES ($1, $2, $3, $4, $5, $6, 'Y', $7, $8, NOW(), NOW()) + RETURNING *`, + [ + categoryCode, + data.codeValue, + data.codeName, + data.codeNameEng || null, + data.description || null, + data.sortOrder || 0, + createdBy, + createdBy, + ] + ); logger.info(`코드 생성 완료: ${categoryCode}.${data.codeValue}`); return code; @@ -278,28 +335,46 @@ export class CommonCodeService { try { // 디버깅: 받은 데이터 로그 logger.info(`코드 수정 데이터:`, { categoryCode, codeValue, data }); - 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, - is_active: - typeof data.isActive === "boolean" - ? data.isActive - ? "Y" - : "N" - : data.isActive, // boolean이면 "Y"/"N"으로 변환 - updated_by: updatedBy, - updated_date: new Date(), - }, - }); + + // 동적 UPDATE 쿼리 생성 + const updateFields: string[] = ["updated_by = $1", "updated_date = NOW()"]; + const values: any[] = [updatedBy]; + let paramIndex = 2; + + if (data.codeName !== undefined) { + updateFields.push(`code_name = $${paramIndex++}`); + values.push(data.codeName); + } + if (data.codeNameEng !== undefined) { + updateFields.push(`code_name_eng = $${paramIndex++}`); + values.push(data.codeNameEng); + } + if (data.description !== undefined) { + updateFields.push(`description = $${paramIndex++}`); + values.push(data.description); + } + if (data.sortOrder !== undefined) { + updateFields.push(`sort_order = $${paramIndex++}`); + values.push(data.sortOrder); + } + if (data.isActive !== undefined) { + const activeValue = + typeof data.isActive === "boolean" + ? data.isActive + ? "Y" + : "N" + : data.isActive; + updateFields.push(`is_active = $${paramIndex++}`); + values.push(activeValue); + } + + const code = await queryOne( + `UPDATE code_info + SET ${updateFields.join(", ")} + WHERE code_category = $${paramIndex++} AND code_value = $${paramIndex} + RETURNING *`, + [...values, categoryCode, codeValue] + ); logger.info(`코드 수정 완료: ${categoryCode}.${codeValue}`); return code; @@ -314,14 +389,10 @@ export class CommonCodeService { */ async deleteCode(categoryCode: string, codeValue: string) { try { - await prisma.code_info.delete({ - where: { - code_category_code_value: { - code_category: categoryCode, - code_value: codeValue, - }, - }, - }); + await query( + `DELETE FROM code_info WHERE code_category = $1 AND code_value = $2`, + [categoryCode, codeValue] + ); logger.info(`코드 삭제 완료: ${categoryCode}.${codeValue}`); } catch (error) { @@ -335,19 +406,18 @@ export class CommonCodeService { */ 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 codes = await query<{ + code_value: string; + code_name: string; + code_name_eng: string | null; + sort_order: number; + }>( + `SELECT code_value, code_name, code_name_eng, sort_order + FROM code_info + WHERE code_category = $1 AND is_active = 'Y' + ORDER BY sort_order ASC, code_value ASC`, + [categoryCode] + ); const options = codes.map((code) => ({ value: code.code_value, @@ -373,13 +443,14 @@ export class CommonCodeService { ) { try { // 먼저 존재하는 코드들을 확인 - const existingCodes = await prisma.code_info.findMany({ - where: { - code_category: categoryCode, - code_value: { in: codes.map((c) => c.codeValue) }, - }, - select: { code_value: true }, - }); + const codeValues = codes.map((c) => c.codeValue); + const placeholders = codeValues.map((_, i) => `$${i + 2}`).join(", "); + + const existingCodes = await query<{ code_value: string }>( + `SELECT code_value FROM code_info + WHERE code_category = $1 AND code_value IN (${placeholders})`, + [categoryCode, ...codeValues] + ); const existingCodeValues = existingCodes.map((c) => c.code_value); const validCodes = codes.filter((c) => @@ -392,23 +463,17 @@ export class CommonCodeService { ); } - const updatePromises = validCodes.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); + // 트랜잭션으로 업데이트 + await transaction(async (client) => { + for (const { codeValue, sortOrder } of validCodes) { + await client.query( + `UPDATE code_info + SET sort_order = $1, updated_by = $2, updated_date = NOW() + WHERE code_category = $3 AND code_value = $4`, + [sortOrder, updatedBy, categoryCode, codeValue] + ); + } + }); const skippedCodes = codes.filter( (c) => !existingCodeValues.includes(c.codeValue) @@ -460,18 +525,38 @@ export class CommonCodeService { break; } - // 수정 시 자기 자신 제외 - if (excludeCategoryCode) { - whereCondition.category_code = { - ...whereCondition.category_code, - not: excludeCategoryCode, - }; + // SQL 쿼리 생성 + let sql = ""; + const values: any[] = []; + let paramIndex = 1; + + switch (field) { + case "categoryCode": + sql = `SELECT category_code FROM code_category WHERE category_code = $${paramIndex++}`; + values.push(trimmedValue); + break; + case "categoryName": + sql = `SELECT category_code FROM code_category WHERE category_name = $${paramIndex++}`; + values.push(trimmedValue); + break; + case "categoryNameEng": + sql = `SELECT category_code FROM code_category WHERE category_name_eng = $${paramIndex++}`; + values.push(trimmedValue); + break; } - const existingCategory = await prisma.code_category.findFirst({ - where: whereCondition, - select: { category_code: true }, - }); + // 수정 시 자기 자신 제외 + if (excludeCategoryCode) { + sql += ` AND category_code != $${paramIndex++}`; + values.push(excludeCategoryCode); + } + + sql += ` LIMIT 1`; + + const existingCategory = await queryOne<{ category_code: string }>( + sql, + values + ); const isDuplicate = !!existingCategory; const fieldNames = { @@ -527,18 +612,35 @@ export class CommonCodeService { break; } - // 수정 시 자기 자신 제외 - if (excludeCodeValue) { - whereCondition.code_value = { - ...whereCondition.code_value, - not: excludeCodeValue, - }; + // SQL 쿼리 생성 + let sql = "SELECT code_value FROM code_info WHERE code_category = $1 AND "; + const values: any[] = [categoryCode]; + let paramIndex = 2; + + switch (field) { + case "codeValue": + sql += `code_value = $${paramIndex++}`; + values.push(trimmedValue); + break; + case "codeName": + sql += `code_name = $${paramIndex++}`; + values.push(trimmedValue); + break; + case "codeNameEng": + sql += `code_name_eng = $${paramIndex++}`; + values.push(trimmedValue); + break; } - const existingCode = await prisma.code_info.findFirst({ - where: whereCondition, - select: { code_value: true }, - }); + // 수정 시 자기 자신 제외 + if (excludeCodeValue) { + sql += ` AND code_value != $${paramIndex++}`; + values.push(excludeCodeValue); + } + + sql += ` LIMIT 1`; + + const existingCode = await queryOne<{ code_value: string }>(sql, values); const isDuplicate = !!existingCode; const fieldNames = {