feat: Phase 3.4 CommonCodeService Raw Query 전환 완료
10개 Prisma 호출을 모두 Raw Query로 전환 - 카테고리 관리 (getCategories, createCategory, updateCategory, deleteCategory) - 코드 관리 (getCodes, createCode, updateCode, deleteCode) - 코드 옵션 조회 (getCodeOptions) - 코드 순서 변경 (reorderCodes) - 중복 검사 (checkCategoryDuplicate, checkCodeDuplicate) 주요 기술적 해결: - 동적 WHERE 조건 생성 (ILIKE 검색, OR 조건) - 동적 UPDATE 쿼리 (변경된 필드만 업데이트) - IN 절 동적 파라미터 바인딩 (reorderCodes) - 트랜잭션 처리 (순서 변경) - 동적 SQL 쿼리 생성 (중복 검사) TypeScript 컴파일 성공 Prisma import 완전 제거 Phase 3 진행률: 64/162 (39.5%) 전체 진행률: 315/444 (70.9%)
This commit is contained in:
parent
a5653eee3e
commit
296340351f
|
|
@ -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<CodeCategory>(
|
||||
`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<CodeInfo>(
|
||||
`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<CodeCategory>(
|
||||
`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<CodeCategory>(
|
||||
`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<CodeInfo>(
|
||||
`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<CodeInfo>(
|
||||
`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 = {
|
||||
|
|
|
|||
Loading…
Reference in New Issue