summaryrefslogtreecommitdiff
path: root/lib/swp/actions.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/swp/actions.ts')
-rw-r--r--lib/swp/actions.ts542
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 : "파일 취소 실패"
- );
- }
-}