diff options
Diffstat (limited to 'lib/legal-review/service.ts')
| -rw-r--r-- | lib/legal-review/service.ts | 738 |
1 files changed, 0 insertions, 738 deletions
diff --git a/lib/legal-review/service.ts b/lib/legal-review/service.ts deleted file mode 100644 index bc55a1fc..00000000 --- a/lib/legal-review/service.ts +++ /dev/null @@ -1,738 +0,0 @@ -'use server' - -import { revalidatePath, unstable_noStore } from "next/cache"; -import db from "@/db/db"; -import { legalWorks, legalWorkRequests, legalWorkResponses, legalWorkAttachments, vendors, legalWorksDetailView } from "@/db/schema"; -import { and, asc, count, desc, eq, ilike, or, SQL, inArray } from "drizzle-orm"; -import { CreateLegalWorkData, GetLegalWorksSchema, createLegalWorkSchema } from "./validations"; -import { filterColumns } from "@/lib/filter-columns"; -import { saveFile } from "../file-stroage"; - -interface CreateLegalWorkResult { - success: boolean; - data?: { - id: number; - message: string; - }; - error?: string; -} - - - -export async function createLegalWork( - data: CreateLegalWorkData -): Promise<CreateLegalWorkResult> { - unstable_noStore(); - - try { - // 1. 입력 데이터 검증 - const validatedData = createLegalWorkSchema.parse(data); - - // 2. 벤더 정보 조회 - const vendor = await db - .select({ - id: vendors.id, - vendorCode: vendors.vendorCode, - vendorName: vendors.vendorName, - }) - .from(vendors) - .where(eq(vendors.id, validatedData.vendorId)) - .limit(1); - - if (!vendor.length) { - return { - success: false, - error: "선택한 벤더를 찾을 수 없습니다.", - }; - } - - const selectedVendor = vendor[0]; - - // 3. 트랜잭션으로 데이터 삽입 - const result = await db.transaction(async (tx) => { - // 3-1. legal_works 테이블에 메인 데이터 삽입 - const [legalWorkResult] = await tx - .insert(legalWorks) - .values({ - category: validatedData.category, - status: "신규등록", // 초기 상태 - vendorId: validatedData.vendorId, - vendorCode: selectedVendor.vendorCode, - vendorName: selectedVendor.vendorName, - isUrgent: validatedData.isUrgent, - requestDate: validatedData.requestDate, - consultationDate: new Date().toISOString().split('T')[0], // 오늘 날짜 - hasAttachment: false, // 초기값 - reviewer: validatedData.reviewer, // 추후 할당 - legalResponder: null, // 추후 할당 - }) - .returning({ id: legalWorks.id }); - - const legalWorkId = legalWorkResult.id; - - - - return { legalWorkId }; - }); - - // 4. 캐시 재검증 - revalidatePath("/legal-works"); - - return { - success: true, - data: { - id: result.legalWorkId, - message: "법무업무가 성공적으로 등록되었습니다.", - }, - }; - - } catch (error) { - console.error("createLegalWork 오류:", error); - - // 데이터베이스 오류 처리 - if (error instanceof Error) { - // 외래키 제약 조건 오류 - if (error.message.includes('foreign key constraint')) { - return { - success: false, - error: "선택한 벤더가 유효하지 않습니다.", - }; - } - - // 중복 키 오류 등 기타 DB 오류 - return { - success: false, - error: "데이터베이스 오류가 발생했습니다.", - }; - } - - return { - success: false, - error: "알 수 없는 오류가 발생했습니다.", - }; - } -} - -// 법무업무 상태 업데이트 함수 (보너스) -export async function updateLegalWorkStatus( - legalWorkId: number, - status: string, - reviewer?: string, - legalResponder?: string -): Promise<CreateLegalWorkResult> { - unstable_noStore(); - - try { - const updateData: Partial<typeof legalWorks.$inferInsert> = { - status, - updatedAt: new Date(), - }; - - if (reviewer) updateData.reviewer = reviewer; - if (legalResponder) updateData.legalResponder = legalResponder; - - await db - .update(legalWorks) - .set(updateData) - .where(eq(legalWorks.id, legalWorkId)); - - revalidatePath("/legal-works"); - - return { - success: true, - data: { - id: legalWorkId, - message: "상태가 성공적으로 업데이트되었습니다.", - }, - }; - - } catch (error) { - console.error("updateLegalWorkStatus 오류:", error); - return { - success: false, - error: "상태 업데이트 중 오류가 발생했습니다.", - }; - } -} - -// 법무업무 삭제 함수 (보너스) -export async function deleteLegalWork(legalWorkId: number): Promise<CreateLegalWorkResult> { - unstable_noStore(); - - try { - await db.transaction(async (tx) => { - // 관련 요청 데이터 먼저 삭제 - await tx - .delete(legalWorkRequests) - .where(eq(legalWorkRequests.legalWorkId, legalWorkId)); - - // 메인 법무업무 데이터 삭제 - await tx - .delete(legalWorks) - .where(eq(legalWorks.id, legalWorkId)); - }); - - revalidatePath("/legal-works"); - - return { - success: true, - data: { - id: legalWorkId, - message: "법무업무가 성공적으로 삭제되었습니다.", - }, - }; - - } catch (error) { - console.error("deleteLegalWork 오류:", error); - return { - success: false, - error: "삭제 중 오류가 발생했습니다.", - }; - } -} - - -export async function getLegalWorks(input: GetLegalWorksSchema) { - unstable_noStore(); // ✅ 1. 캐싱 방지 추가 - - try { - const offset = (input.page - 1) * input.perPage; - - // ✅ 2. 안전한 필터 처리 (getEvaluationTargets와 동일) - let advancedWhere: SQL<unknown> | undefined = undefined; - - if (input.filters && Array.isArray(input.filters) && input.filters.length > 0) { - console.log("필터 적용:", input.filters.map(f => `${f.id} ${f.operator} ${f.value}`)); - - try { - advancedWhere = filterColumns({ - table: legalWorksDetailView, - filters: input.filters, - joinOperator: input.joinOperator || 'and', - }); - - console.log("필터 조건 생성 완료"); - } catch (error) { - console.error("필터 조건 생성 오류:", error); - // ✅ 필터 오류 시에도 전체 데이터 반환 - advancedWhere = undefined; - } - } - - // ✅ 3. 안전한 글로벌 검색 처리 - let globalWhere: SQL<unknown> | undefined = undefined; - if (input.search) { - const searchTerm = `%${input.search}%`; - - const searchConditions: SQL<unknown>[] = [ - ilike(legalWorksDetailView.vendorCode, searchTerm), - ilike(legalWorksDetailView.vendorName, searchTerm), - ilike(legalWorksDetailView.title, searchTerm), - ilike(legalWorksDetailView.requestContent, searchTerm), - ilike(legalWorksDetailView.reviewer, searchTerm), - ilike(legalWorksDetailView.legalResponder, searchTerm) - ].filter(Boolean); - - if (searchConditions.length > 0) { - globalWhere = or(...searchConditions); - } - } - - // ✅ 4. 안전한 WHERE 조건 결합 - const whereConditions: SQL<unknown>[] = []; - if (advancedWhere) whereConditions.push(advancedWhere); - if (globalWhere) whereConditions.push(globalWhere); - - const finalWhere = whereConditions.length > 0 ? and(...whereConditions) : undefined; - - // ✅ 5. 전체 데이터 수 조회 - const totalResult = await db - .select({ count: count() }) - .from(legalWorksDetailView) - .where(finalWhere); - - const total = totalResult[0]?.count || 0; - - if (total === 0) { - return { data: [], pageCount: 0, total: 0 }; - } - - console.log("총 데이터 수:", total); - - // ✅ 6. 정렬 및 페이징 처리 - const orderByColumns = input.sort.map((sort) => { - const column = sort.id as keyof typeof legalWorksDetailView.$inferSelect; - return sort.desc - ? desc(legalWorksDetailView[column]) - : asc(legalWorksDetailView[column]); - }); - - if (orderByColumns.length === 0) { - orderByColumns.push(desc(legalWorksDetailView.createdAt)); - } - - const legalWorksData = await db - .select() - .from(legalWorksDetailView) - .where(finalWhere) - .orderBy(...orderByColumns) - .limit(input.perPage) - .offset(offset); - - const pageCount = Math.ceil(total / input.perPage); - - console.log("반환 데이터 수:", legalWorksData.length); - - return { data: legalWorksData, pageCount, total }; - } catch (err) { - console.error("getLegalWorks 오류:", err); - return { data: [], pageCount: 0, total: 0 }; - } -} -// 특정 법무업무 상세 조회 -export async function getLegalWorkById(id: number) { - unstable_noStore(); - - try { - const result = await db - .select() - .from(legalWorksDetailView) - .where(eq(legalWorksDetailView.id , id)) - .limit(1); - - return result[0] || null; - } catch (error) { - console.error("getLegalWorkById 오류:", error); - return null; - } -} - -// 법무업무 통계 (뷰 테이블 사용) -export async function getLegalWorksStats() { - unstable_noStore(); - try { - // 전체 통계 - const totalStats = await db - .select({ - total: count(), - category: legalWorksDetailView.category, - status: legalWorksDetailView.status, - isUrgent: legalWorksDetailView.isUrgent, - }) - .from(legalWorksDetailView); - - // 통계 데이터 가공 - const stats = { - total: totalStats.length, - byCategory: {} as Record<string, number>, - byStatus: {} as Record<string, number>, - urgent: 0, - }; - - totalStats.forEach(stat => { - // 카테고리별 집계 - if (stat.category) { - stats.byCategory[stat.category] = (stats.byCategory[stat.category] || 0) + 1; - } - - // 상태별 집계 - if (stat.status) { - stats.byStatus[stat.status] = (stats.byStatus[stat.status] || 0) + 1; - } - - // 긴급 건수 - if (stat.isUrgent) { - stats.urgent++; - } - }); - - return stats; - } catch (error) { - console.error("getLegalWorksStatsSimple 오류:", error); - return { - total: 0, - byCategory: {}, - byStatus: {}, - urgent: 0, - }; - } -} - -// 검토요청 폼 데이터 타입 -interface RequestReviewData { - // 기본 설정 - dueDate: string - assignee?: string - notificationMethod: "email" | "internal" | "both" - - // 법무업무 상세 정보 - reviewDepartment: "준법문의" | "법무검토" - inquiryType?: "국내계약" | "국내자문" | "해외계약" | "해외자문" - - // 공통 필드 - title: string - requestContent: string - - // 준법문의 전용 필드 - isPublic?: boolean - - // 법무검토 전용 필드들 - contractProjectName?: string - contractType?: string - contractCounterparty?: string - counterpartyType?: "법인" | "개인" - contractPeriod?: string - contractAmount?: string - factualRelation?: string - projectNumber?: string - shipownerOrderer?: string - projectType?: string - governingLaw?: string -} - -// 첨부파일 업로드 함수 -async function uploadAttachment(file: File, legalWorkId: number, userId?: string) { - try { - console.log(`📎 첨부파일 업로드 시작: ${file.name} (${file.size} bytes)`) - - const result = await saveFile({ - file, - directory: "legal-works", - originalName: file.name, - userId: userId || "system" - }) - - if (!result.success) { - throw new Error(result.error || "파일 업로드 실패") - } - - console.log(`✅ 첨부파일 업로드 성공: ${result.fileName}`) - - return { - fileName: result.fileName!, - originalFileName: result.originalName!, - filePath: result.publicPath!, - fileSize: result.fileSize!, - mimeType: file.type, - securityChecks: result.securityChecks - } - } catch (error) { - console.error(`❌ 첨부파일 업로드 실패: ${file.name}`, error) - throw error - } -} - - -export async function requestReview( - legalWorkId: number, - formData: RequestReviewData, - attachments: File[] = [], - userId?: string -) { - try { - console.log(`🚀 검토요청 처리 시작 - 법무업무 #${legalWorkId}`) - - // 트랜잭션 시작 - const result = await db.transaction(async (tx) => { - // 1. legal_works 테이블 업데이트 - const [updatedWork] = await tx - .update(legalWorks) - .set({ - status: "검토요청", - expectedAnswerDate: formData.dueDate, - hasAttachment: attachments.length > 0, - updatedAt: new Date(), - }) - .where(eq(legalWorks.id, legalWorkId)) - .returning() - - if (!updatedWork) { - throw new Error("법무업무를 찾을 수 없습니다.") - } - - console.log(`📝 법무업무 상태 업데이트 완료: ${updatedWork.status}`) - - // 2. legal_work_requests 테이블에 데이터 삽입 - const [createdRequest] = await tx - .insert(legalWorkRequests) - .values({ - legalWorkId: legalWorkId, - reviewDepartment: formData.reviewDepartment, - inquiryType: formData.inquiryType || null, - title: formData.title, - requestContent: formData.requestContent, - - // 준법문의 관련 필드 - isPublic: formData.reviewDepartment === "준법문의" ? (formData.isPublic || false) : null, - - // 법무검토 관련 필드들 - contractProjectName: formData.contractProjectName || null, - contractType: formData.contractType || null, - contractAmount: formData.contractAmount ? parseFloat(formData.contractAmount) : null, - - // 국내계약 전용 필드들 - contractCounterparty: formData.contractCounterparty || null, - counterpartyType: formData.counterpartyType || null, - contractPeriod: formData.contractPeriod || null, - - // 자문 관련 필드 - factualRelation: formData.factualRelation || null, - - // 해외 관련 필드들 - projectNumber: formData.projectNumber || null, - shipownerOrderer: formData.shipownerOrderer || null, - governingLaw: formData.governingLaw || null, - projectType: formData.projectType || null, - }) - .returning() - - console.log(`📋 검토요청 정보 저장 완료: ${createdRequest.reviewDepartment}`) - - // 3. 첨부파일 처리 - const uploadedFiles = [] - const failedFiles = [] - - if (attachments.length > 0) { - console.log(`📎 첨부파일 처리 시작: ${attachments.length}개`) - - for (const file of attachments) { - try { - const uploadResult = await uploadAttachment(file, legalWorkId, userId) - - // DB에 첨부파일 정보 저장 - const [attachmentRecord] = await tx - .insert(legalWorkAttachments) - .values({ - legalWorkId: legalWorkId, - fileName: uploadResult.fileName, - originalFileName: uploadResult.originalFileName, - filePath: uploadResult.filePath, - fileSize: uploadResult.fileSize, - mimeType: uploadResult.mimeType, - attachmentType: 'request', - isAutoGenerated: false, - }) - .returning() - - uploadedFiles.push({ - id: attachmentRecord.id, - name: uploadResult.originalFileName, - size: uploadResult.fileSize, - securityChecks: uploadResult.securityChecks - }) - - } catch (fileError) { - console.error(`❌ 파일 업로드 실패: ${file.name}`, fileError) - failedFiles.push({ - name: file.name, - error: fileError instanceof Error ? fileError.message : "업로드 실패" - }) - } - } - - console.log(`✅ 파일 업로드 완료: 성공 ${uploadedFiles.length}개, 실패 ${failedFiles.length}개`) - } - - return { - updatedWork, - createdRequest, - uploadedFiles, - failedFiles, - totalFiles: attachments.length, - } - }) - - // 페이지 재검증 - revalidatePath("/legal-works") - - // 성공 메시지 구성 - let message = `검토요청이 성공적으로 발송되었습니다.` - - if (result.totalFiles > 0) { - message += ` (첨부파일: 성공 ${result.uploadedFiles.length}개` - if (result.failedFiles.length > 0) { - message += `, 실패 ${result.failedFiles.length}개` - } - message += `)` - } - - console.log(`🎉 검토요청 처리 완료 - 법무업무 #${legalWorkId}`) - - return { - success: true, - data: { - message, - legalWorkId: legalWorkId, - requestId: result.createdRequest.id, - uploadedFiles: result.uploadedFiles, - failedFiles: result.failedFiles, - } - } - - } catch (error) { - console.error(`💥 검토요청 처리 중 오류 - 법무업무 #${legalWorkId}:`, error) - - return { - success: false, - error: error instanceof Error ? error.message : "검토요청 처리 중 오류가 발생했습니다." - } - } -} - - -// FormData를 사용하는 버전 (파일 업로드용) -export async function requestReviewWithFiles(formData: FormData) { - try { - // 기본 데이터 추출 - const legalWorkId = parseInt(formData.get("legalWorkId") as string) - - const requestData: RequestReviewData = { - dueDate: formData.get("dueDate") as string, - assignee: formData.get("assignee") as string || undefined, - notificationMethod: formData.get("notificationMethod") as "email" | "internal" | "both", - reviewDepartment: formData.get("reviewDepartment") as "준법문의" | "법무검토", - inquiryType: formData.get("inquiryType") as "국내계약" | "국내자문" | "해외계약" | "해외자문" || undefined, - title: formData.get("title") as string, - requestContent: formData.get("requestContent") as string, - isPublic: formData.get("isPublic") === "true", - - // 법무검토 관련 필드들 - contractProjectName: formData.get("contractProjectName") as string || undefined, - contractType: formData.get("contractType") as string || undefined, - contractCounterparty: formData.get("contractCounterparty") as string || undefined, - counterpartyType: formData.get("counterpartyType") as "법인" | "개인" || undefined, - contractPeriod: formData.get("contractPeriod") as string || undefined, - contractAmount: formData.get("contractAmount") as string || undefined, - factualRelation: formData.get("factualRelation") as string || undefined, - projectNumber: formData.get("projectNumber") as string || undefined, - shipownerOrderer: formData.get("shipownerOrderer") as string || undefined, - projectType: formData.get("projectType") as string || undefined, - governingLaw: formData.get("governingLaw") as string || undefined, - } - - // 첨부파일 추출 - const attachments: File[] = [] - for (const [key, value] of formData.entries()) { - if (key.startsWith("attachment_") && value instanceof File && value.size > 0) { - attachments.push(value) - } - } - - return await requestReview(legalWorkId, requestData, attachments) - - } catch (error) { - console.error("FormData 처리 중 오류:", error) - return { - success: false, - error: "요청 데이터 처리 중 오류가 발생했습니다." - } - } -} - -// 검토요청 가능 여부 확인 -export async function canRequestReview(legalWorkId: number) { - try { - const [work] = await db - .select({ status: legalWorks.status }) - .from(legalWorks) - .where(eq(legalWorks.id, legalWorkId)) - .limit(1) - - if (!work) { - return { canRequest: false, reason: "법무업무를 찾을 수 없습니다." } - } - - if (work.status !== "신규등록") { - return { - canRequest: false, - reason: `현재 상태(${work.status})에서는 검토요청을 할 수 없습니다. 신규등록 상태에서만 가능합니다.` - } - } - - return { canRequest: true } - - } catch (error) { - console.error("검토요청 가능 여부 확인 중 오류:", error) - return { - canRequest: false, - reason: "상태 확인 중 오류가 발생했습니다." - } - } -} - -// 삭제 요청 타입 -interface RemoveLegalWorksInput { - ids: number[] -} - -// 응답 타입 -interface RemoveLegalWorksResponse { - error?: string - success?: boolean -} - -/** - * 법무업무 삭제 서버 액션 - */ -export async function removeLegalWorks({ - ids, -}: RemoveLegalWorksInput): Promise<RemoveLegalWorksResponse> { - try { - // 유효성 검사 - if (!ids || ids.length === 0) { - return { - error: "삭제할 법무업무를 선택해주세요.", - } - } - - // 삭제 가능한 상태인지 확인 (선택적) - const existingWorks = await db - .select({ id: legalWorks.id, status: legalWorks.status }) - .from(legalWorks) - .where(inArray(legalWorks.id, ids)) - - // 삭제 불가능한 상태 체크 (예: 진행중인 업무는 삭제 불가) - const nonDeletableWorks = existingWorks.filter( - work => work.status === "검토중" || work.status === "담당자배정" - ) - - if (nonDeletableWorks.length > 0) { - return { - error: "진행중인 법무업무는 삭제할 수 없습니다.", - } - } - - // 실제 삭제 실행 - const result = await db - .delete(legalWorks) - .where(inArray(legalWorks.id, ids)) - - // 결과 확인 - if (result.changes === 0) { - return { - error: "삭제할 법무업무를 찾을 수 없습니다.", - } - } - - // 캐시 재검증 - revalidatePath("/legal-works") // 실제 경로에 맞게 수정 - - return { - success: true, - } - - } catch (error) { - console.error("법무업무 삭제 중 오류 발생:", error) - - return { - error: "법무업무 삭제 중 오류가 발생했습니다. 다시 시도해주세요.", - } - } -} - -/** - * 단일 법무업무 삭제 (선택적) - */ -export async function removeLegalWork(id: number): Promise<RemoveLegalWorksResponse> { - return removeLegalWorks({ ids: [id] }) -}
\ No newline at end of file |
