diff options
| author | 0-Zz-ang <s1998319@gmail.com> | 2025-11-19 09:50:12 +0900 |
|---|---|---|
| committer | 0-Zz-ang <s1998319@gmail.com> | 2025-11-19 09:50:12 +0900 |
| commit | 2f1bef8eeff5d6cd30c4de808402893deb35335d (patch) | |
| tree | b3a286bca25df3a038ace20969855b8485878b7b /lib/compliance/services.ts | |
| parent | 84277bd79bd6a2bff0f6ef6840f1790db06036e6 (diff) | |
준법설문조사 리비전관리
Diffstat (limited to 'lib/compliance/services.ts')
| -rw-r--r-- | lib/compliance/services.ts | 189 |
1 files changed, 174 insertions, 15 deletions
diff --git a/lib/compliance/services.ts b/lib/compliance/services.ts index a603a091..8dc8e916 100644 --- a/lib/compliance/services.ts +++ b/lib/compliance/services.ts @@ -1,7 +1,7 @@ 'use server' import db from "@/db/db"; -import { eq, desc, count, and, ne, or, ilike, asc } from "drizzle-orm"; +import { eq, desc, count, and, ne, or, ilike, asc, inArray } from "drizzle-orm"; import { revalidatePath } from "next/cache"; import { complianceSurveyTemplates, @@ -787,24 +787,182 @@ export async function updateComplianceResponseStatus(responseId: number, status: } // 설문조사 템플릿 생성 +const DEFAULT_TEMPLATE_VERSION = "1.0"; + +function incrementVersionString(version?: string | null) { + if (!version) { + return DEFAULT_TEMPLATE_VERSION; + } + const numericValue = Number(version); + if (!Number.isNaN(numericValue)) { + const hasDecimal = version.includes("."); + const decimalDigits = hasDecimal ? version.split(".")[1]?.length ?? 0 : 0; + const incremented = numericValue + 1; + return hasDecimal ? incremented.toFixed(decimalDigits) : String(incremented); + } + return DEFAULT_TEMPLATE_VERSION; +} + export async function createComplianceSurveyTemplate(data: { name: string; description: string; - version: string; isActive?: boolean; + baseTemplateId?: number | null; }) { try { - const [template] = await db - .insert(complianceSurveyTemplates) - .values({ - name: data.name, - description: data.description, - version: data.version, - isActive: data.isActive ?? true, - }) - .returning(); + return await db.transaction(async (tx) => { + let baseTemplate: + | typeof complianceSurveyTemplates.$inferSelect + | undefined; + + if (data.baseTemplateId) { + const [explicitBase] = await tx + .select() + .from(complianceSurveyTemplates) + .where(eq(complianceSurveyTemplates.id, data.baseTemplateId)); + + if (!explicitBase) { + throw new Error("BASE_TEMPLATE_NOT_FOUND"); + } + baseTemplate = explicitBase; + } else { + const [latestSameName] = await tx + .select() + .from(complianceSurveyTemplates) + .where(eq(complianceSurveyTemplates.name, data.name)) + .orderBy(desc(complianceSurveyTemplates.createdAt)) + .limit(1); + + if (latestSameName) { + baseTemplate = latestSameName; + } + } - return template; + let version = DEFAULT_TEMPLATE_VERSION; + if (baseTemplate) { + version = incrementVersionString(baseTemplate.version); + } + + const [template] = await tx + .insert(complianceSurveyTemplates) + .values({ + name: data.name, + description: data.description, + version, + isActive: data.isActive ?? true, + updatedAt: new Date(), + }) + .returning(); + + if (baseTemplate) { + const questions = await tx + .select({ + id: complianceQuestions.id, + questionNumber: complianceQuestions.questionNumber, + questionText: complianceQuestions.questionText, + questionType: complianceQuestions.questionType, + isRequired: complianceQuestions.isRequired, + isRedFlag: complianceQuestions.isRedFlag, + hasDetailText: complianceQuestions.hasDetailText, + hasFileUpload: complianceQuestions.hasFileUpload, + parentQuestionId: complianceQuestions.parentQuestionId, + conditionalValue: complianceQuestions.conditionalValue, + displayOrder: complianceQuestions.displayOrder, + }) + .from(complianceQuestions) + .where(eq(complianceQuestions.templateId, baseTemplate.id)) + .orderBy(complianceQuestions.displayOrder); + + const questionIdMap = new Map<number, number>(); + let pending = [...questions]; + let safetyCounter = 0; + + while (pending.length > 0) { + if (safetyCounter > pending.length * 2) { + throw new Error("QUESTION_PARENT_MAPPING_FAILED"); + } + const nextPending: typeof pending = []; + + for (const question of pending) { + if ( + question.parentQuestionId && + !questionIdMap.has(question.parentQuestionId) + ) { + nextPending.push(question); + continue; + } + + const parentId = question.parentQuestionId + ? questionIdMap.get(question.parentQuestionId) ?? null + : null; + + const [newQuestion] = await tx + .insert(complianceQuestions) + .values({ + templateId: template.id, + questionNumber: question.questionNumber, + questionText: question.questionText, + questionType: question.questionType, + isRequired: question.isRequired, + isRedFlag: question.isRedFlag, + hasDetailText: question.hasDetailText, + hasFileUpload: question.hasFileUpload, + parentQuestionId: parentId, + conditionalValue: question.conditionalValue, + displayOrder: question.displayOrder, + }) + .returning({ id: complianceQuestions.id }); + + questionIdMap.set(question.id, newQuestion.id); + } + + if (nextPending.length === pending.length) { + throw new Error("QUESTION_PARENT_RESOLUTION_FAILED"); + } + + pending = nextPending; + safetyCounter += 1; + } + + if (questionIdMap.size > 0) { + const questionIds = Array.from(questionIdMap.keys()); + const options = await tx + .select({ + questionId: complianceQuestionOptions.questionId, + optionValue: complianceQuestionOptions.optionValue, + optionText: complianceQuestionOptions.optionText, + allowsOtherInput: complianceQuestionOptions.allowsOtherInput, + displayOrder: complianceQuestionOptions.displayOrder, + }) + .from(complianceQuestionOptions) + .where(inArray(complianceQuestionOptions.questionId, questionIds)); + + for (const option of options) { + const newQuestionId = questionIdMap.get(option.questionId); + if (!newQuestionId) continue; + + await tx.insert(complianceQuestionOptions).values({ + questionId: newQuestionId, + optionValue: option.optionValue, + optionText: option.optionText, + allowsOtherInput: option.allowsOtherInput, + displayOrder: option.displayOrder, + }); + } + } + + await tx + .update(complianceSurveyTemplates) + .set({ + isActive: false, + updatedAt: new Date(), + }) + .where(eq(complianceSurveyTemplates.id, baseTemplate.id)); + } + + revalidatePath("/evcp/compliance"); + return template; + }); } catch (error) { console.error("Error creating compliance survey template:", error); throw error; @@ -821,13 +979,14 @@ export async function createTemplateAction(formData: FormData) { const name = formData.get("name") as string const description = formData.get("description") as string - const version = formData.get("version") as string const isActive = formData.get("isActive") === "true" + const baseTemplateIdValue = formData.get("baseTemplateId") as string | null + const baseTemplateId = baseTemplateIdValue ? Number(baseTemplateIdValue) : undefined // 필수 필드 검증 - if (!name || !description || !version) { + if (!name || !description) { return { error: "필수 필드가 누락되었습니다." } } @@ -835,8 +994,8 @@ export async function createTemplateAction(formData: FormData) { await createComplianceSurveyTemplate({ name, description, - version, isActive, + baseTemplateId: baseTemplateId && !Number.isNaN(baseTemplateId) ? baseTemplateId : undefined, }) // 페이지 캐시 무효화 |
