From 1dc24d48e52f2e490f5603ceb02842586ecae533 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Thu, 24 Jul 2025 11:06:32 +0000 Subject: (대표님) 정기평가 피드백 반영, 설계 피드백 반영, (최겸) 기술영업 피드백 반영 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/basic-contract/service.ts | 230 +++++++++++++++++++++++++++++++++--------- 1 file changed, 185 insertions(+), 45 deletions(-) (limited to 'lib/basic-contract/service.ts') diff --git a/lib/basic-contract/service.ts b/lib/basic-contract/service.ts index 87a861e1..014f32ab 100644 --- a/lib/basic-contract/service.ts +++ b/lib/basic-contract/service.ts @@ -49,8 +49,6 @@ export async function addTemplate( if (templateData instanceof FormData) { const templateName = templateData.get("templateName") as string; // 문자열을 숫자로 변환 (FormData의 get은 항상 string|File을 반환) - const validityPeriodStr = templateData.get("validityPeriod") as string; - const validityPeriod = validityPeriodStr ? parseInt(validityPeriodStr, 10) : 12; // 기본값 12개월 const status = templateData.get("status") as "ACTIVE" | "INACTIVE"; const file = templateData.get("file") as File; @@ -63,12 +61,6 @@ export async function addTemplate( return { success: false, error: "파일은 필수입니다." }; } - if (isNaN(validityPeriod) || validityPeriod < 1 || validityPeriod > 120) { - return { - success: false, - error: "유효기간은 1~120개월 사이의 유효한 값이어야 합니다." - }; - } const saveResult = await saveFile({file, directory:"basicContract/template" }); if (!saveResult.success) { @@ -79,7 +71,6 @@ export async function addTemplate( const formattedData = { templateName, status, - validityPeriod, // 숫자로 변환된 유효기간 fileName: file.name, filePath: saveResult.publicPath! }; @@ -196,26 +187,31 @@ export async function getBasicContractTemplates( // 템플릿 생성 (서버 액션) -export async function createBasicContractTemplate( - input: CreateBasicContractTemplateSchema -) { +export async function createBasicContractTemplate(input: CreateBasicContractTemplateSchema) { unstable_noStore(); + try { const newTemplate = await db.transaction(async (tx) => { - const [newTemplate] = await insertBasicContractTemplate(tx, { + const [row] = await insertBasicContractTemplate(tx, { templateName: input.templateName, - validityPeriod: input.validityPeriod, + revision: 1, + legalReviewRequired: input.legalReviewRequired, + shipBuildingApplicable: input.shipBuildingApplicable, + windApplicable: input.windApplicable, + pcApplicable: input.pcApplicable, + nbApplicable: input.nbApplicable, + rcApplicable: input.rcApplicable, + gyApplicable: input.gyApplicable, + sysApplicable: input.sysApplicable, + infraApplicable: input.infraApplicable, status: input.status, fileName: input.fileName, filePath: input.filePath, + // 필요하면 createdAt/updatedAt 등도 여기서 }); - return newTemplate; + return row; }); - // 캐시 무효화 - revalidateTag("basic-contract-templates"); - revalidateTag("template-status-counts"); - return { data: newTemplate, error: null }; } catch (error) { return { data: null, error: getErrorMessage(error) }; @@ -350,6 +346,23 @@ interface UpdateTemplateParams { formData: FormData; } +const SCOPE_KEYS = [ + "shipBuildingApplicable", + "windApplicable", + "pcApplicable", + "nbApplicable", + "rcApplicable", + "gyApplicable", + "sysApplicable", + "infraApplicable", +] as const; + +function getBool(fd: FormData, key: string, defaultValue = false) { + const v = fd.get(key); + if (v === null) return defaultValue; + return v === "true"; +} + export async function updateTemplate({ id, formData @@ -357,51 +370,76 @@ export async function updateTemplate({ unstable_noStore(); try { - const templateName = formData.get("templateName") as string; - const validityPeriodStr = formData.get("validityPeriod") as string; - const validityPeriod = validityPeriodStr ? parseInt(validityPeriodStr, 10) : 12; - const status = formData.get("status") as "ACTIVE" | "INACTIVE"; - const file = formData.get("file") as File | null; - + // 필수값 + const templateName = formData.get("templateName") as string | null; if (!templateName) { return { error: "템플릿 이름은 필수입니다." }; } - // 기본 업데이트 데이터 - const updateData: Record = { - templateName, - status, - validityPeriod, - updatedAt: new Date(), - }; + // 선택/추가 필드 파싱 + const revisionStr = formData.get("revision")?.toString() ?? "1"; + const revision = Number(revisionStr) || 1; + + const legalReviewRequired = getBool(formData, "legalReviewRequired", false); + + // status는 프런트에서 ACTIVE만 넣고 있으나, 없으면 기존값 유지 or 기본값 설정 + const status = (formData.get("status") as "ACTIVE" | "INACTIVE" | null) ?? "ACTIVE"; + // validityPeriod가 이제 필요없다면 제거하시고, 사용한다면 파싱 그대로 + const validityPeriodStr = formData.get("validityPeriod")?.toString(); + const validityPeriod = validityPeriodStr ? Number(validityPeriodStr) : undefined; + + // Scope booleans + const scopeData: Record = {}; + for (const key of SCOPE_KEYS) { + scopeData[key] = getBool(formData, key, false); + } + + // 파일 처리 + const file = formData.get("file") as File | null; + let fileName: string | undefined = undefined; + let filePath: string | undefined = undefined; - // 파일이 있는 경우 처리 if (file) { - const saveResult = await saveFile({file,directory:"basicContract/template"}); + // 1) 새 파일 저장 + const saveResult = await saveFile({ file, directory: "basicContract/template" }); if (!saveResult.success) { return { success: false, error: saveResult.error }; } + fileName = file.name; + filePath = saveResult.publicPath; - // 기존 파일 정보 가져오기 + // 2) 기존 파일 삭제 const existingTemplate = await db.query.basicContractTemplates.findFirst({ - where: eq(basicContractTemplates.id, id) + where: eq(basicContractTemplates.id, id), }); - // 기존 파일이 있다면 삭제 if (existingTemplate?.filePath) { - const deleted = await deleteFile(existingTemplate.filePath); if (deleted) { - console.log(`✅ 파일 삭제됨: ${existingTemplate.filePath}`); + console.log(`✅ 기존 파일 삭제됨: ${existingTemplate.filePath}`); } else { - console.log(`⚠️ 파일 삭제 실패: ${existingTemplate.filePath}`); + console.log(`⚠️ 기존 파일 삭제 실패: ${existingTemplate.filePath}`); } } + } - // 업데이트 데이터에 파일 정보 추가 - updateData.fileName = file.name; - updateData.filePath = saveResult.publicPath; + // 업데이트할 데이터 구성 + const updateData: Record = { + templateName, + revision, + legalReviewRequired, + status, + updatedAt: new Date(), + ...scopeData, + }; + + if (validityPeriod !== undefined) { + updateData.validityPeriod = validityPeriod; + } + if (fileName && filePath) { + updateData.fileName = fileName; + updateData.filePath = filePath; } // DB 업데이트 @@ -412,7 +450,7 @@ export async function updateTemplate({ .where(eq(basicContractTemplates.id, id)); }); - // 캐시 무효화 (다양한 방법 시도) + // 캐시 무효화 revalidateTag("basic-contract-templates"); revalidateTag("template-status-counts"); revalidateTag("templates"); @@ -423,7 +461,7 @@ export async function updateTemplate({ return { error: error instanceof Error ? error.message - : "템플릿 업데이트 중 오류가 발생했습니다." + : "템플릿 업데이트 중 오류가 발생했습니다.", }; } } @@ -987,4 +1025,106 @@ export async function saveTemplateFile(templateId: number, formData: FormData) { export async function refreshTemplatePage(templateId: string) { revalidatePath(`/evcp/basic-contract-template/${templateId}`); revalidateTag("basic-contract-templates"); +} + +// 새 리비전 생성 함수 +export async function createBasicContractTemplateRevision(input: CreateRevisionSchema) { + unstable_noStore(); + + try { + // 기본 템플릿 존재 확인 + const baseTemplate = await db + .select() + .from(basicContractTemplates) + .where(eq(basicContractTemplates.id, input.baseTemplateId)) + .limit(1); + + if (baseTemplate.length === 0) { + return { data: null, error: "기본 템플릿을 찾을 수 없습니다." }; + } + + // 같은 템플릿 이름에 해당 리비전이 이미 존재하는지 확인 + const existingRevision = await db + .select() + .from(basicContractTemplates) + .where( + and( + eq(basicContractTemplates.templateName, input.templateName), + eq(basicContractTemplates.revision, input.revision) + ) + ) + .limit(1); + + if (existingRevision.length > 0) { + return { + data: null, + error: `${input.templateName} v${input.revision} 리비전이 이미 존재합니다.` + }; + } + + // 새 리비전이 기존 리비전들보다 큰 번호인지 확인 + const maxRevision = await db + .select({ maxRev: basicContractTemplates.revision }) + .from(basicContractTemplates) + .where(eq(basicContractTemplates.templateName, input.templateName)) + .orderBy(desc(basicContractTemplates.revision)) + .limit(1); + + if (maxRevision.length > 0 && input.revision <= maxRevision[0].maxRev) { + return { + data: null, + error: `새 리비전 번호는 현재 최대 리비전(v${maxRevision[0].maxRev})보다 커야 합니다.` + }; + } + + const newRevision = await db.transaction(async (tx) => { + const [row] = await insertBasicContractTemplate(tx, { + templateName: input.templateName, + revision: input.revision, + legalReviewRequired: input.legalReviewRequired, + shipBuildingApplicable: input.shipBuildingApplicable, + windApplicable: input.windApplicable, + pcApplicable: input.pcApplicable, + nbApplicable: input.nbApplicable, + rcApplicable: input.rcApplicable, + gyApplicable: input.gyApplicable, + sysApplicable: input.sysApplicable, + infraApplicable: input.infraApplicable, + status: "ACTIVE", + fileName: input.fileName, + filePath: input.filePath, + validityPeriod: null, + }); + return row; + }); + + return { data: newRevision, error: null }; + } catch (error) { + return { data: null, error: getErrorMessage(error) }; + } +} + + + + +// 1) 전체 basicContractTemplates 조회 +export async function getALLBasicContractTemplates() { + return db + .select() + .from(basicContractTemplates) + .where(eq(basicContractTemplates.status,"ACTIVE")) + .orderBy(desc(basicContractTemplates.createdAt)); +} + +// 2) 등록된 templateName만 중복 없이 가져오기 +export async function getExistingTemplateNames(): Promise { + const rows = await db + .select({ + templateName: basicContractTemplates.templateName, + }) + .from(basicContractTemplates) + .where(eq(basicContractTemplates.status,"ACTIVE")) + .groupBy(basicContractTemplates.templateName); + + return rows.map((r) => r.templateName); } \ No newline at end of file -- cgit v1.2.3