summaryrefslogtreecommitdiff
path: root/lib/legal-review/service.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/legal-review/service.ts')
-rw-r--r--lib/legal-review/service.ts738
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