feat: Phase 3.9 TemplateStandardService Raw Query 전환 완료
7개 Prisma 호출을 모두 Raw Query로 전환 - 템플릿 목록 조회 (getTemplates - 복잡한 OR 조건, Promise.all) - 템플릿 단건 조회 (getTemplate) - 템플릿 생성 (createTemplate - 중복 검사) - 템플릿 수정 (updateTemplate - 동적 UPDATE, 11개 필드) - 템플릿 삭제 (deleteTemplate) - 정렬 순서 일괄 업데이트 (updateSortOrder - Promise.all) - 카테고리 목록 조회 (getCategories - DISTINCT) 주요 기술적 해결: - 복잡한 OR 조건 처리 (is_public OR company_code) - 동적 WHERE 조건 생성 (ILIKE 다중 검색) - 동적 UPDATE 쿼리 (11개 필드 조건부 업데이트) - DISTINCT 쿼리 (카테고리 목록) - Promise.all 병렬 쿼리 (목록 + 개수 동시 조회) - Promise.all 병렬 업데이트 (정렬 순서 일괄 업데이트) TypeScript 컴파일 성공 Prisma import 완전 제거 Phase 3 진행률: 114/162 (70.4%) 전체 진행률: 365/444 (82.2%)
This commit is contained in:
@@ -1,6 +1,4 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
import { query, queryOne } from "../database/db";
|
||||
|
||||
/**
|
||||
* 템플릿 표준 관리 서비스
|
||||
@@ -30,42 +28,57 @@ export class TemplateStandardService {
|
||||
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
// 기본 필터 조건
|
||||
const where: any = {};
|
||||
// 동적 WHERE 조건 생성
|
||||
const conditions: string[] = [];
|
||||
const values: any[] = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
if (active && active !== "all") {
|
||||
where.is_active = active;
|
||||
conditions.push(`is_active = $${paramIndex++}`);
|
||||
values.push(active);
|
||||
}
|
||||
|
||||
if (category && category !== "all") {
|
||||
where.category = category;
|
||||
conditions.push(`category = $${paramIndex++}`);
|
||||
values.push(category);
|
||||
}
|
||||
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ template_name: { contains: search, mode: "insensitive" } },
|
||||
{ template_name_eng: { contains: search, mode: "insensitive" } },
|
||||
{ description: { contains: search, mode: "insensitive" } },
|
||||
];
|
||||
conditions.push(
|
||||
`(template_name ILIKE $${paramIndex} OR template_name_eng ILIKE $${paramIndex} OR description ILIKE $${paramIndex})`
|
||||
);
|
||||
values.push(`%${search}%`);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
// 회사별 필터링 (공개 템플릿 + 해당 회사 템플릿)
|
||||
// 회사별 필터링
|
||||
if (company_code) {
|
||||
where.OR = [{ is_public: "Y" }, { company_code: company_code }];
|
||||
conditions.push(`(is_public = 'Y' OR company_code = $${paramIndex++})`);
|
||||
values.push(company_code);
|
||||
} else if (is_public === "Y") {
|
||||
where.is_public = "Y";
|
||||
conditions.push(`is_public = $${paramIndex++}`);
|
||||
values.push("Y");
|
||||
}
|
||||
|
||||
const [templates, total] = await Promise.all([
|
||||
prisma.template_standards.findMany({
|
||||
where,
|
||||
orderBy: [{ sort_order: "asc" }, { template_name: "asc" }],
|
||||
skip,
|
||||
take: limit,
|
||||
}),
|
||||
prisma.template_standards.count({ where }),
|
||||
const whereClause =
|
||||
conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
||||
|
||||
const [templates, totalResult] = await Promise.all([
|
||||
query<any>(
|
||||
`SELECT * FROM template_standards
|
||||
${whereClause}
|
||||
ORDER BY sort_order ASC, template_name ASC
|
||||
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
|
||||
[...values, limit, skip]
|
||||
),
|
||||
queryOne<{ count: string }>(
|
||||
`SELECT COUNT(*) as count FROM template_standards ${whereClause}`,
|
||||
values
|
||||
),
|
||||
]);
|
||||
|
||||
const total = parseInt(totalResult?.count || "0");
|
||||
|
||||
return { templates, total };
|
||||
}
|
||||
|
||||
@@ -73,9 +86,10 @@ export class TemplateStandardService {
|
||||
* 템플릿 상세 조회
|
||||
*/
|
||||
async getTemplate(templateCode: string) {
|
||||
return await prisma.template_standards.findUnique({
|
||||
where: { template_code: templateCode },
|
||||
});
|
||||
return await queryOne<any>(
|
||||
`SELECT * FROM template_standards WHERE template_code = $1`,
|
||||
[templateCode]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,9 +97,10 @@ export class TemplateStandardService {
|
||||
*/
|
||||
async createTemplate(templateData: any) {
|
||||
// 템플릿 코드 중복 확인
|
||||
const existing = await prisma.template_standards.findUnique({
|
||||
where: { template_code: templateData.template_code },
|
||||
});
|
||||
const existing = await queryOne<any>(
|
||||
`SELECT * FROM template_standards WHERE template_code = $1`,
|
||||
[templateData.template_code]
|
||||
);
|
||||
|
||||
if (existing) {
|
||||
throw new Error(
|
||||
@@ -93,83 +108,101 @@ export class TemplateStandardService {
|
||||
);
|
||||
}
|
||||
|
||||
return await prisma.template_standards.create({
|
||||
data: {
|
||||
template_code: templateData.template_code,
|
||||
template_name: templateData.template_name,
|
||||
template_name_eng: templateData.template_name_eng,
|
||||
description: templateData.description,
|
||||
category: templateData.category,
|
||||
icon_name: templateData.icon_name,
|
||||
default_size: templateData.default_size,
|
||||
layout_config: templateData.layout_config,
|
||||
preview_image: templateData.preview_image,
|
||||
sort_order: templateData.sort_order || 0,
|
||||
is_active: templateData.is_active || "Y",
|
||||
is_public: templateData.is_public || "N",
|
||||
company_code: templateData.company_code,
|
||||
created_by: templateData.created_by,
|
||||
updated_by: templateData.updated_by,
|
||||
},
|
||||
});
|
||||
return await queryOne<any>(
|
||||
`INSERT INTO template_standards
|
||||
(template_code, template_name, template_name_eng, description, category,
|
||||
icon_name, default_size, layout_config, preview_image, sort_order,
|
||||
is_active, is_public, company_code, created_by, updated_by, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, NOW(), NOW())
|
||||
RETURNING *`,
|
||||
[
|
||||
templateData.template_code,
|
||||
templateData.template_name,
|
||||
templateData.template_name_eng,
|
||||
templateData.description,
|
||||
templateData.category,
|
||||
templateData.icon_name,
|
||||
templateData.default_size,
|
||||
templateData.layout_config,
|
||||
templateData.preview_image,
|
||||
templateData.sort_order || 0,
|
||||
templateData.is_active || "Y",
|
||||
templateData.is_public || "N",
|
||||
templateData.company_code,
|
||||
templateData.created_by,
|
||||
templateData.updated_by,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 수정
|
||||
*/
|
||||
async updateTemplate(templateCode: string, templateData: any) {
|
||||
const updateData: any = {};
|
||||
// 동적 UPDATE 쿼리 생성
|
||||
const updateFields: string[] = ["updated_at = NOW()"];
|
||||
const values: any[] = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
// 수정 가능한 필드들만 업데이트
|
||||
if (templateData.template_name !== undefined) {
|
||||
updateData.template_name = templateData.template_name;
|
||||
updateFields.push(`template_name = $${paramIndex++}`);
|
||||
values.push(templateData.template_name);
|
||||
}
|
||||
if (templateData.template_name_eng !== undefined) {
|
||||
updateData.template_name_eng = templateData.template_name_eng;
|
||||
updateFields.push(`template_name_eng = $${paramIndex++}`);
|
||||
values.push(templateData.template_name_eng);
|
||||
}
|
||||
if (templateData.description !== undefined) {
|
||||
updateData.description = templateData.description;
|
||||
updateFields.push(`description = $${paramIndex++}`);
|
||||
values.push(templateData.description);
|
||||
}
|
||||
if (templateData.category !== undefined) {
|
||||
updateData.category = templateData.category;
|
||||
updateFields.push(`category = $${paramIndex++}`);
|
||||
values.push(templateData.category);
|
||||
}
|
||||
if (templateData.icon_name !== undefined) {
|
||||
updateData.icon_name = templateData.icon_name;
|
||||
updateFields.push(`icon_name = $${paramIndex++}`);
|
||||
values.push(templateData.icon_name);
|
||||
}
|
||||
if (templateData.default_size !== undefined) {
|
||||
updateData.default_size = templateData.default_size;
|
||||
updateFields.push(`default_size = $${paramIndex++}`);
|
||||
values.push(templateData.default_size);
|
||||
}
|
||||
if (templateData.layout_config !== undefined) {
|
||||
updateData.layout_config = templateData.layout_config;
|
||||
updateFields.push(`layout_config = $${paramIndex++}`);
|
||||
values.push(templateData.layout_config);
|
||||
}
|
||||
if (templateData.preview_image !== undefined) {
|
||||
updateData.preview_image = templateData.preview_image;
|
||||
updateFields.push(`preview_image = $${paramIndex++}`);
|
||||
values.push(templateData.preview_image);
|
||||
}
|
||||
if (templateData.sort_order !== undefined) {
|
||||
updateData.sort_order = templateData.sort_order;
|
||||
updateFields.push(`sort_order = $${paramIndex++}`);
|
||||
values.push(templateData.sort_order);
|
||||
}
|
||||
if (templateData.is_active !== undefined) {
|
||||
updateData.is_active = templateData.is_active;
|
||||
updateFields.push(`is_active = $${paramIndex++}`);
|
||||
values.push(templateData.is_active);
|
||||
}
|
||||
if (templateData.is_public !== undefined) {
|
||||
updateData.is_public = templateData.is_public;
|
||||
updateFields.push(`is_public = $${paramIndex++}`);
|
||||
values.push(templateData.is_public);
|
||||
}
|
||||
if (templateData.updated_by !== undefined) {
|
||||
updateData.updated_by = templateData.updated_by;
|
||||
updateFields.push(`updated_by = $${paramIndex++}`);
|
||||
values.push(templateData.updated_by);
|
||||
}
|
||||
|
||||
updateData.updated_date = new Date();
|
||||
|
||||
try {
|
||||
return await prisma.template_standards.update({
|
||||
where: { template_code: templateCode },
|
||||
data: updateData,
|
||||
});
|
||||
return await queryOne<any>(
|
||||
`UPDATE template_standards
|
||||
SET ${updateFields.join(", ")}
|
||||
WHERE template_code = $${paramIndex}
|
||||
RETURNING *`,
|
||||
[...values, templateCode]
|
||||
);
|
||||
} catch (error: any) {
|
||||
if (error.code === "P2025") {
|
||||
return null; // 템플릿을 찾을 수 없음
|
||||
}
|
||||
throw error;
|
||||
return null; // 템플릿을 찾을 수 없음
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,15 +211,12 @@ export class TemplateStandardService {
|
||||
*/
|
||||
async deleteTemplate(templateCode: string) {
|
||||
try {
|
||||
await prisma.template_standards.delete({
|
||||
where: { template_code: templateCode },
|
||||
});
|
||||
await query(`DELETE FROM template_standards WHERE template_code = $1`, [
|
||||
templateCode,
|
||||
]);
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
if (error.code === "P2025") {
|
||||
return false; // 템플릿을 찾을 수 없음
|
||||
}
|
||||
throw error;
|
||||
return false; // 템플릿을 찾을 수 없음
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,13 +227,12 @@ export class TemplateStandardService {
|
||||
templates: { template_code: string; sort_order: number }[]
|
||||
) {
|
||||
const updatePromises = templates.map((template) =>
|
||||
prisma.template_standards.update({
|
||||
where: { template_code: template.template_code },
|
||||
data: {
|
||||
sort_order: template.sort_order,
|
||||
updated_date: new Date(),
|
||||
},
|
||||
})
|
||||
query(
|
||||
`UPDATE template_standards
|
||||
SET sort_order = $1, updated_at = NOW()
|
||||
WHERE template_code = $2`,
|
||||
[template.sort_order, template.template_code]
|
||||
)
|
||||
);
|
||||
|
||||
await Promise.all(updatePromises);
|
||||
@@ -259,15 +288,14 @@ export class TemplateStandardService {
|
||||
* 템플릿 카테고리 목록 조회
|
||||
*/
|
||||
async getCategories(companyCode: string) {
|
||||
const categories = await prisma.template_standards.findMany({
|
||||
where: {
|
||||
OR: [{ is_public: "Y" }, { company_code: companyCode }],
|
||||
is_active: "Y",
|
||||
},
|
||||
select: { category: true },
|
||||
distinct: ["category"],
|
||||
orderBy: { category: "asc" },
|
||||
});
|
||||
const categories = await query<{ category: string }>(
|
||||
`SELECT DISTINCT category
|
||||
FROM template_standards
|
||||
WHERE (is_public = $1 OR company_code = $2)
|
||||
AND is_active = $3
|
||||
ORDER BY category ASC`,
|
||||
["Y", companyCode, "Y"]
|
||||
);
|
||||
|
||||
return categories.map((item) => item.category).filter(Boolean);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user