summaryrefslogtreecommitdiff
path: root/lib/compliance/services.ts
diff options
context:
space:
mode:
author0-Zz-ang <s1998319@gmail.com>2025-11-19 09:50:12 +0900
committer0-Zz-ang <s1998319@gmail.com>2025-11-19 09:50:12 +0900
commit2f1bef8eeff5d6cd30c4de808402893deb35335d (patch)
treeb3a286bca25df3a038ace20969855b8485878b7b /lib/compliance/services.ts
parent84277bd79bd6a2bff0f6ef6840f1790db06036e6 (diff)
준법설문조사 리비전관리
Diffstat (limited to 'lib/compliance/services.ts')
-rw-r--r--lib/compliance/services.ts189
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,
})
// 페이지 캐시 무효화