diff options
Diffstat (limited to 'lib/swp/actions.ts')
| -rw-r--r-- | lib/swp/actions.ts | 542 |
1 files changed, 0 insertions, 542 deletions
diff --git a/lib/swp/actions.ts b/lib/swp/actions.ts deleted file mode 100644 index 6962884e..00000000 --- a/lib/swp/actions.ts +++ /dev/null @@ -1,542 +0,0 @@ -"use server"; - -import db from "@/db/db"; -import { swpDocuments, swpDocumentRevisions, swpDocumentFiles } from "@/db/schema/SWP/swp-documents"; -import { eq, and, sql, like, desc, asc, type SQL } from "drizzle-orm"; -import { fetchSwpProjectData } from "./api-client"; -import { syncSwpProject } from "./sync-service"; -import * as fs from "fs/promises"; -import * as path from "path"; -import { debugLog, debugError, debugWarn, debugSuccess } from "@/lib/debug-utils"; - -// ============================================================================ -// 타입 정의 -// ============================================================================ - -export interface SwpTableFilters { - projNo?: string; - docNo?: string; - docTitle?: string; - pkgNo?: string; - vndrCd?: string; - stage?: string; -} - -export interface SwpTableParams { - page: number; - pageSize: number; - sortBy?: string; - sortOrder?: "asc" | "desc"; - filters?: SwpTableFilters; -} - -export interface SwpDocumentWithStats { - DOC_NO: string; - DOC_TITLE: string; - PROJ_NO: string; - PROJ_NM: string | null; - PKG_NO: string | null; - VNDR_CD: string | null; - CPY_NM: string | null; - LTST_REV_NO: string | null; - STAGE: string | null; - LTST_ACTV_STAT: string | null; - sync_status: "synced" | "pending" | "error"; - last_synced_at: Date; - revision_count: number; - file_count: number; -} - -// ============================================================================ -// 서버 액션: 문서 목록 조회 (페이지네이션 + 검색) -// ============================================================================ - -export async function fetchSwpDocuments(params: SwpTableParams) { - const { page, pageSize, sortBy = "last_synced_at", sortOrder = "desc", filters } = params; - const offset = (page - 1) * pageSize; - - try { - // WHERE 조건 구성 - const conditions: SQL<unknown>[] = []; - - if (filters?.projNo) { - conditions.push(like(swpDocuments.PROJ_NO, `%${filters.projNo}%`)); - } - if (filters?.docNo) { - conditions.push(like(swpDocuments.DOC_NO, `%${filters.docNo}%`)); - } - if (filters?.docTitle) { - conditions.push(like(swpDocuments.DOC_TITLE, `%${filters.docTitle}%`)); - } - if (filters?.pkgNo) { - conditions.push(like(swpDocuments.PKG_NO, `%${filters.pkgNo}%`)); - } - if (filters?.vndrCd) { - conditions.push(like(swpDocuments.VNDR_CD, `%${filters.vndrCd}%`)); - } - if (filters?.stage) { - conditions.push(eq(swpDocuments.STAGE, filters.stage)); - } - - const whereClause = conditions.length > 0 ? and(...conditions) : undefined; - - // 총 개수 조회 - const totalResult = await db - .select({ count: sql<number>`count(*)::int` }) - .from(swpDocuments) - .where(whereClause); - - const total = totalResult[0]?.count || 0; - - // 정렬 컬럼 결정 - const orderByColumn = - sortBy === "DOC_NO" ? swpDocuments.DOC_NO : - sortBy === "DOC_TITLE" ? swpDocuments.DOC_TITLE : - sortBy === "PROJ_NO" ? swpDocuments.PROJ_NO : - sortBy === "PKG_NO" ? swpDocuments.PKG_NO : - sortBy === "STAGE" ? swpDocuments.STAGE : - swpDocuments.last_synced_at; - - // 데이터 조회 (Drizzle query builder 사용) - const documents = await db - .select({ - DOC_NO: swpDocuments.DOC_NO, - DOC_TITLE: swpDocuments.DOC_TITLE, - PROJ_NO: swpDocuments.PROJ_NO, - PROJ_NM: swpDocuments.PROJ_NM, - PKG_NO: swpDocuments.PKG_NO, - VNDR_CD: swpDocuments.VNDR_CD, - CPY_NM: swpDocuments.CPY_NM, - LTST_REV_NO: swpDocuments.LTST_REV_NO, - STAGE: swpDocuments.STAGE, - LTST_ACTV_STAT: swpDocuments.LTST_ACTV_STAT, - sync_status: swpDocuments.sync_status, - last_synced_at: swpDocuments.last_synced_at, - revision_count: sql<number>`COUNT(DISTINCT ${swpDocumentRevisions.id})::int`, - file_count: sql<number>`COUNT(DISTINCT ${swpDocumentFiles.id})::int`, - }) - .from(swpDocuments) - .leftJoin(swpDocumentRevisions, eq(swpDocuments.DOC_NO, swpDocumentRevisions.DOC_NO)) - .leftJoin(swpDocumentFiles, eq(swpDocumentRevisions.id, swpDocumentFiles.revision_id)) - .where(whereClause) - .groupBy( - swpDocuments.DOC_NO, - swpDocuments.DOC_TITLE, - swpDocuments.PROJ_NO, - swpDocuments.PROJ_NM, - swpDocuments.PKG_NO, - swpDocuments.VNDR_CD, - swpDocuments.CPY_NM, - swpDocuments.LTST_REV_NO, - swpDocuments.STAGE, - swpDocuments.LTST_ACTV_STAT, - swpDocuments.sync_status, - swpDocuments.last_synced_at - ) - .orderBy(sortOrder === "desc" ? desc(orderByColumn) : asc(orderByColumn)) - .limit(pageSize) - .offset(offset); - - return { - data: documents, - total, - page, - pageSize, - totalPages: Math.ceil(total / pageSize), - }; - } catch (error) { - console.error("[fetchSwpDocuments] 오류:", error); - throw new Error("문서 목록 조회 실패 [SWP API에서 실패가 발생했습니다. 담당자에게 문의하세요]"); - } -} - -// ============================================================================ -// 서버 액션: 문서의 리비전 목록 조회 -// ============================================================================ - -export async function fetchDocumentRevisions(docNo: string) { - try { - const revisions = await db - .select({ - id: swpDocumentRevisions.id, - DOC_NO: swpDocumentRevisions.DOC_NO, - REV_NO: swpDocumentRevisions.REV_NO, - STAGE: swpDocumentRevisions.STAGE, - ACTV_NO: swpDocumentRevisions.ACTV_NO, - OFDC_NO: swpDocumentRevisions.OFDC_NO, - sync_status: swpDocumentRevisions.sync_status, - last_synced_at: swpDocumentRevisions.last_synced_at, - file_count: sql<number>`COUNT(DISTINCT ${swpDocumentFiles.id})::int`, - }) - .from(swpDocumentRevisions) - .leftJoin(swpDocumentFiles, eq(swpDocumentRevisions.id, swpDocumentFiles.revision_id)) - .where(eq(swpDocumentRevisions.DOC_NO, docNo)) - .groupBy( - swpDocumentRevisions.id, - swpDocumentRevisions.DOC_NO, - swpDocumentRevisions.REV_NO, - swpDocumentRevisions.STAGE, - swpDocumentRevisions.ACTV_NO, - swpDocumentRevisions.OFDC_NO, - swpDocumentRevisions.sync_status, - swpDocumentRevisions.last_synced_at - ) - .orderBy(desc(swpDocumentRevisions.REV_NO)); - - return revisions; - } catch (error) { - console.error("[fetchDocumentRevisions] 오류:", error); - throw new Error("리비전 목록 조회 실패"); - } -} - -// ============================================================================ -// 서버 액션: 리비전의 파일 목록 조회 -// ============================================================================ - -export async function fetchRevisionFiles(revisionId: number) { - try { - const files = await db - .select({ - id: swpDocumentFiles.id, - FILE_NM: swpDocumentFiles.FILE_NM, - FILE_SEQ: swpDocumentFiles.FILE_SEQ, - FILE_SZ: swpDocumentFiles.FILE_SZ, - FLD_PATH: swpDocumentFiles.FLD_PATH, - STAT: swpDocumentFiles.STAT, - STAT_NM: swpDocumentFiles.STAT_NM, - sync_status: swpDocumentFiles.sync_status, - created_at: swpDocumentFiles.created_at, - }) - .from(swpDocumentFiles) - .where(eq(swpDocumentFiles.revision_id, revisionId)) - .orderBy(asc(swpDocumentFiles.FILE_SEQ)); - - return files; - } catch (error) { - console.error("[fetchRevisionFiles] 오류:", error); - throw new Error("파일 목록 조회 실패"); - } -} - -// ============================================================================ -// 서버 액션: 프로젝트 동기화 -// ============================================================================ - -export async function syncSwpProjectAction(projectNo: string, docGb: "M" | "V" = "V") { - try { - console.log(`[syncSwpProjectAction] 시작: ${projectNo}`); - - // 1. API에서 데이터 조회 - const { documents, files } = await fetchSwpProjectData(projectNo, docGb); - - // 2. 동기화 실행 - const result = await syncSwpProject(projectNo, documents, files); - - console.log(`[syncSwpProjectAction] 완료:`, result.stats); - - return result; - } catch (error) { - console.error("[syncSwpProjectAction] 오류:", error); - throw new Error( - error instanceof Error ? error.message : "동기화 실패" - ); - } -} - -// ============================================================================ -// 서버 액션: 프로젝트 목록 조회 (필터용) -// ============================================================================ - -export async function fetchProjectList() { - try { - const projects = await db - .select({ - PROJ_NO: swpDocuments.PROJ_NO, - PROJ_NM: swpDocuments.PROJ_NM, - doc_count: sql<number>`COUNT(DISTINCT ${swpDocuments.DOC_NO})::int`, - }) - .from(swpDocuments) - .groupBy(swpDocuments.PROJ_NO, swpDocuments.PROJ_NM) - .orderBy(desc(sql`COUNT(DISTINCT ${swpDocuments.DOC_NO})`)); - - return projects; - } catch (error) { - console.error("[fetchProjectList] 오류:", error); - return []; - } -} - -// ============================================================================ -// 서버 액션: 통계 조회 -// ============================================================================ - -export async function fetchSwpStats(projNo?: string) { - try { - const whereClause = projNo ? eq(swpDocuments.PROJ_NO, projNo) : undefined; - - const stats = await db - .select({ - total_documents: sql<number>`COUNT(DISTINCT ${swpDocuments.DOC_NO})::int`, - total_revisions: sql<number>`COUNT(DISTINCT ${swpDocumentRevisions.id})::int`, - total_files: sql<number>`COUNT(DISTINCT ${swpDocumentFiles.id})::int`, - last_sync: sql<Date>`MAX(${swpDocuments.last_synced_at})`, - }) - .from(swpDocuments) - .leftJoin(swpDocumentRevisions, eq(swpDocuments.DOC_NO, swpDocumentRevisions.DOC_NO)) - .leftJoin(swpDocumentFiles, eq(swpDocumentRevisions.id, swpDocumentFiles.revision_id)) - .where(whereClause); - - return stats[0] || { - total_documents: 0, - total_revisions: 0, - total_files: 0, - last_sync: null, - }; - } catch (error) { - console.error("[fetchSwpStats] 오류:", error); - return { - total_documents: 0, - total_revisions: 0, - total_files: 0, - last_sync: null, - }; - } -} - -// ============================================================================ -// 서버 액션: 파일 다운로드 -// ============================================================================ - -export interface DownloadFileResult { - success: boolean; - data?: Uint8Array; - fileName?: string; - mimeType?: string; - error?: string; -} - -export async function downloadSwpFile(fileId: number): Promise<DownloadFileResult> { - try { - debugLog(`[downloadSwpFile] 다운로드 시작`, { fileId }); - - // 1. 파일 정보 조회 - const fileInfo = await db - .select({ - FILE_NM: swpDocumentFiles.FILE_NM, - FLD_PATH: swpDocumentFiles.FLD_PATH, - }) - .from(swpDocumentFiles) - .where(eq(swpDocumentFiles.id, fileId)) - .limit(1); - - if (!fileInfo || fileInfo.length === 0) { - debugError(`[downloadSwpFile] 파일 정보 없음`, { fileId }); - return { - success: false, - error: "파일 정보를 찾을 수 없습니다.", - }; - } - - const { FILE_NM, FLD_PATH } = fileInfo[0]; - debugLog(`[downloadSwpFile] 파일 정보 조회 완료`, { FILE_NM, FLD_PATH }); - - if (!FLD_PATH || !FILE_NM) { - debugError(`[downloadSwpFile] 파일 경로 또는 이름 없음`, { FILE_NM, FLD_PATH }); - return { - success: false, - error: "파일 경로 또는 파일명이 없습니다.", - }; - } - - // 2. NFS 마운트 경로 확인 - const nfsBasePath = process.env.SWP_MOUNT_DIR; - if (!nfsBasePath) { - console.error( - '[downloadSwpFile] SWP_MOUNT_DIR 환경변수가 설정되지 않았습니다.' - ); - return { - success: false, - error: "서버 설정 오류: NFS 경로가 설정되지 않았습니다.", - }; - } - - // 3. 전체 파일 경로 생성 - // FLD_PATH가 절대 경로일 수도 있고 상대 경로일 수도 있으므로 처리 - // Windows 스타일 백슬래시를 리눅스 슬래시로 변환 - const normalizedFldPath = FLD_PATH.replace(/\\/g, '/'); - const fullPath = path.join(nfsBasePath, normalizedFldPath, FILE_NM); - - console.log("[downloadSwpFile] 파일 다운로드 시도:", { - fileId, - FILE_NM, - FLD_PATH, - normalizedFldPath, - fullPath, - }); - - // 4. 파일 존재 여부 확인 - try { - await fs.access(fullPath, fs.constants.R_OK); - } catch (accessError) { - console.error("[downloadSwpFile] 파일 접근 불가:", accessError); - return { - success: false, - error: `파일을 찾을 수 없습니다: ${FILE_NM}`, - }; - } - - // 5. 파일 읽기 - debugLog(`[downloadSwpFile] 파일 읽기 시작`, { fullPath }); - const fileBuffer = await fs.readFile(fullPath); - - debugLog(`[downloadSwpFile] 파일 Buffer 읽기 완료`, { - bufferLength: fileBuffer.length, - isBuffer: Buffer.isBuffer(fileBuffer), - bufferType: typeof fileBuffer, - constructor: fileBuffer.constructor.name, - first20Bytes: fileBuffer.slice(0, 20).toString('hex') - }); - - const fileData = new Uint8Array(fileBuffer); - - debugLog(`[downloadSwpFile] Uint8Array 변환 완료`, { - uint8ArrayLength: fileData.length, - uint8ArrayType: typeof fileData, - constructor: fileData.constructor.name, - first20Bytes: Array.from(fileData.slice(0, 20)), - jsonStringified: JSON.stringify(fileData).substring(0, 100) + '...' - }); - - // 6. MIME 타입 결정 - const mimeType = getMimeType(FILE_NM); - - console.log("[downloadSwpFile] 파일 다운로드 성공:", { - fileName: FILE_NM, - size: fileData.length, - mimeType, - }); - - debugSuccess(`[downloadSwpFile] 다운로드 성공`, { - fileName: FILE_NM, - dataLength: fileData.length, - mimeType, - returnDataType: typeof fileData, - isUint8Array: fileData instanceof Uint8Array - }); - - return { - success: true, - data: fileData, - fileName: FILE_NM, - mimeType, - }; - } catch (error) { - console.error("[downloadSwpFile] 오류:", error); - debugError(`[downloadSwpFile] 다운로드 실패`, { - error: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined - }); - return { - success: false, - error: error instanceof Error ? error.message : "파일 다운로드 실패", - }; - } -} - -// ============================================================================ -// 헬퍼: MIME 타입 결정 -// ============================================================================ - -function getMimeType(fileName: string): string { - const ext = path.extname(fileName).toLowerCase(); - - const mimeTypes: Record<string, string> = { - ".pdf": "application/pdf", - ".doc": "application/msword", - ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - ".xls": "application/vnd.ms-excel", - ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - ".ppt": "application/vnd.ms-powerpoint", - ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation", - ".txt": "text/plain", - ".csv": "text/csv", - ".jpg": "image/jpeg", - ".jpeg": "image/jpeg", - ".png": "image/png", - ".gif": "image/gif", - ".zip": "application/zip", - ".rar": "application/x-rar-compressed", - ".7z": "application/x-7z-compressed", - ".dwg": "application/acad", - ".dxf": "application/dxf", - }; - - return mimeTypes[ext] || "application/octet-stream"; -} - -// ============================================================================ -// 서버 액션: 벤더 업로드 파일 목록 조회 -// ============================================================================ - -export async function fetchVendorUploadedFiles(projNo: string, vndrCd: string) { - try { - debugLog(`[fetchVendorUploadedFiles] 조회 시작`, { projNo, vndrCd }); - - // fetchGetExternalInboxList 호출 - const { fetchGetExternalInboxList } = await import("./api-client"); - const files = await fetchGetExternalInboxList({ - projNo, - vndrCd, - }); - - debugLog(`[fetchVendorUploadedFiles] 조회 완료`, { - fileCount: files.length - }); - - return files; - } catch (error) { - debugError(`[fetchVendorUploadedFiles] 조회 실패`, { error }); - throw new Error( - error instanceof Error ? error.message : "업로드 파일 목록 조회 실패" - ); - } -} - -// ============================================================================ -// 서버 액션: 벤더 업로드 파일 취소 -// ============================================================================ - -export interface CancelUploadedFileParams { - boxSeq: string; - actvSeq: string; - userId: string; -} - -export async function cancelVendorUploadedFile(params: CancelUploadedFileParams) { - try { - debugLog(`[cancelVendorUploadedFile] 취소 시작`, params); - - const { callSaveInBoxListCancelStatus } = await import("./api-client"); - const cancelCount = await callSaveInBoxListCancelStatus({ - boxSeq: params.boxSeq, - actvSeq: params.actvSeq, - chgr: `evcp${params.userId}`, - }); - - debugSuccess(`[cancelVendorUploadedFile] 취소 완료`, { - ...params, - cancelCount - }); - - return { - success: true, - cancelCount - }; - } catch (error) { - debugError(`[cancelVendorUploadedFile] 취소 실패`, { error }); - throw new Error( - error instanceof Error ? error.message : "파일 취소 실패" - ); - } -} |
