import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); /** * 템플릿 표준 관리 서비스 */ export class TemplateStandardService { /** * 템플릿 목록 조회 */ async getTemplates(params: { active?: string; category?: string; search?: string; company_code?: string; is_public?: string; page?: number; limit?: number; }) { const { active = "Y", category, search, company_code, is_public = "Y", page = 1, limit = 50, } = params; const skip = (page - 1) * limit; // 기본 필터 조건 const where: any = {}; if (active && active !== "all") { where.is_active = active; } if (category && category !== "all") { where.category = category; } if (search) { where.OR = [ { template_name: { contains: search, mode: "insensitive" } }, { template_name_eng: { contains: search, mode: "insensitive" } }, { description: { contains: search, mode: "insensitive" } }, ]; } // 회사별 필터링 (공개 템플릿 + 해당 회사 템플릿) if (company_code) { where.OR = [{ is_public: "Y" }, { company_code: company_code }]; } else if (is_public === "Y") { where.is_public = "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 }), ]); return { templates, total }; } /** * 템플릿 상세 조회 */ async getTemplate(templateCode: string) { return await prisma.template_standards.findUnique({ where: { template_code: templateCode }, }); } /** * 템플릿 생성 */ async createTemplate(templateData: any) { // 템플릿 코드 중복 확인 const existing = await prisma.template_standards.findUnique({ where: { template_code: templateData.template_code }, }); if (existing) { throw new Error( `템플릿 코드 '${templateData.template_code}'는 이미 존재합니다.` ); } 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, }, }); } /** * 템플릿 수정 */ async updateTemplate(templateCode: string, templateData: any) { const updateData: any = {}; // 수정 가능한 필드들만 업데이트 if (templateData.template_name !== undefined) { updateData.template_name = templateData.template_name; } if (templateData.template_name_eng !== undefined) { updateData.template_name_eng = templateData.template_name_eng; } if (templateData.description !== undefined) { updateData.description = templateData.description; } if (templateData.category !== undefined) { updateData.category = templateData.category; } if (templateData.icon_name !== undefined) { updateData.icon_name = templateData.icon_name; } if (templateData.default_size !== undefined) { updateData.default_size = templateData.default_size; } if (templateData.layout_config !== undefined) { updateData.layout_config = templateData.layout_config; } if (templateData.preview_image !== undefined) { updateData.preview_image = templateData.preview_image; } if (templateData.sort_order !== undefined) { updateData.sort_order = templateData.sort_order; } if (templateData.is_active !== undefined) { updateData.is_active = templateData.is_active; } if (templateData.is_public !== undefined) { updateData.is_public = templateData.is_public; } if (templateData.updated_by !== undefined) { updateData.updated_by = templateData.updated_by; } updateData.updated_date = new Date(); try { return await prisma.template_standards.update({ where: { template_code: templateCode }, data: updateData, }); } catch (error: any) { if (error.code === "P2025") { return null; // 템플릿을 찾을 수 없음 } throw error; } } /** * 템플릿 삭제 */ async deleteTemplate(templateCode: string) { try { await prisma.template_standards.delete({ where: { template_code: templateCode }, }); return true; } catch (error: any) { if (error.code === "P2025") { return false; // 템플릿을 찾을 수 없음 } throw error; } } /** * 템플릿 정렬 순서 일괄 업데이트 */ async updateSortOrder( 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(), }, }) ); await Promise.all(updatePromises); } /** * 템플릿 복제 */ async duplicateTemplate(params: { originalCode: string; newCode: string; newName: string; company_code: string; created_by: string; }) { const { originalCode, newCode, newName, company_code, created_by } = params; // 원본 템플릿 조회 const originalTemplate = await this.getTemplate(originalCode); if (!originalTemplate) { throw new Error("원본 템플릿을 찾을 수 없습니다."); } // 새 템플릿 코드 중복 확인 const existing = await this.getTemplate(newCode); if (existing) { throw new Error(`템플릿 코드 '${newCode}'는 이미 존재합니다.`); } // 템플릿 복제 return await this.createTemplate({ template_code: newCode, template_name: newName, template_name_eng: originalTemplate.template_name_eng ? `${originalTemplate.template_name_eng} (Copy)` : undefined, description: originalTemplate.description, category: originalTemplate.category, icon_name: originalTemplate.icon_name, default_size: originalTemplate.default_size, layout_config: originalTemplate.layout_config, preview_image: originalTemplate.preview_image, sort_order: 0, is_active: "Y", is_public: "N", // 복제된 템플릿은 기본적으로 비공개 company_code, created_by, updated_by: created_by, }); } /** * 템플릿 카테고리 목록 조회 */ 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" }, }); return categories.map((item) => item.category).filter(Boolean); } /** * 기본 템플릿 데이터 삽입 (초기 설정용) */ async seedDefaultTemplates() { const defaultTemplates = [ { template_code: "advanced-data-table", template_name: "고급 데이터 테이블", template_name_eng: "Advanced Data Table", description: "컬럼 설정, 필터링, 페이지네이션이 포함된 완전한 데이터 테이블", category: "table", icon_name: "table", default_size: { width: 1000, height: 680 }, layout_config: { components: [ { type: "datatable", label: "데이터 테이블", position: { x: 0, y: 0 }, size: { width: 1000, height: 680 }, style: { border: "1px solid #e5e7eb", borderRadius: "8px", backgroundColor: "#ffffff", padding: "16px", }, }, ], }, sort_order: 1, is_active: "Y", is_public: "Y", company_code: "*", created_by: "system", updated_by: "system", }, { template_code: "universal-button", template_name: "버튼", template_name_eng: "Universal Button", description: "다양한 기능을 설정할 수 있는 범용 버튼. 상세설정에서 기능을 선택하세요.", category: "button", icon_name: "mouse-pointer", default_size: { width: 80, height: 36 }, layout_config: { components: [ { type: "widget", widgetType: "button", label: "버튼", position: { x: 0, y: 0 }, size: { width: 80, height: 36 }, style: { backgroundColor: "#3b82f6", color: "#ffffff", border: "none", borderRadius: "6px", fontSize: "14px", fontWeight: "500", }, }, ], }, sort_order: 2, is_active: "Y", is_public: "Y", company_code: "*", created_by: "system", updated_by: "system", }, { template_code: "file-upload", template_name: "파일 첨부", template_name_eng: "File Upload", description: "드래그앤드롭 파일 업로드 영역", category: "file", icon_name: "upload", default_size: { width: 300, height: 120 }, layout_config: { components: [ { type: "widget", widgetType: "file", label: "파일 첨부", position: { x: 0, y: 0 }, size: { width: 300, height: 120 }, style: { border: "2px dashed #d1d5db", borderRadius: "8px", backgroundColor: "#f9fafb", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "14px", color: "#6b7280", }, }, ], }, sort_order: 3, is_active: "Y", is_public: "Y", company_code: "*", created_by: "system", updated_by: "system", }, ]; // 기존 데이터가 있는지 확인 후 삽입 for (const template of defaultTemplates) { const existing = await this.getTemplate(template.template_code); if (!existing) { await this.createTemplate(template); } } } } export const templateStandardService = new TemplateStandardService();