"use server"; import type { SwpDocumentApiResponse, SwpFileApiResponse, } from "./sync-service"; // ============================================================================ // SWP API 클라이언트 // ============================================================================ const SWP_BASE_URL = process.env.SWP_API_URL || "http://60.100.99.217/DDC"; // ============================================================================ // API 요청 타입 정의 // ============================================================================ /** * 문서 리스트 조회 필터 */ export interface GetVDRDocumentListFilter { proj_no: string; // 필수 doc_gb: "M" | "V"; // 필수 (M=MDR, V=VDR) ctgry?: string; // HULL or TOP pkgNo?: string; vndrCd?: string; pic_deptcd?: string; doc_type?: string; displn?: string; mat_cd?: string; proj_nm?: string; stage?: string; own_doc_no?: string; doc_title?: string; lang_gb?: string; } /** * 첨부파일 리스트 조회 필터 */ export interface GetExternalInboxListFilter { projNo: string; // 필수 pkgNo?: string; vndrCd?: string; stage?: string; owndocno?: string; doctitle?: string; } // ============================================================================ // 공통 API 호출 함수 // ============================================================================ async function callSwpApi( endpoint: string, body: Record, resultKey: string ): Promise { const url = `${SWP_BASE_URL}/Services/WebService.svc/${endpoint}`; console.log(`[SWP API] 호출: ${endpoint}`, body); try { const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(body), signal: AbortSignal.timeout(30000), // 30초 }); if (!response.ok) { throw new Error( `SWP API 오류: ${response.status} ${response.statusText}` ); } const data = await response.json(); if (!data[resultKey]) { throw new Error(`API 응답 형식 오류: ${resultKey} 없음`); } console.log(`[SWP API] 성공: ${data[resultKey].length}개 조회`); return data[resultKey] as T[]; } catch (error) { if (error instanceof Error) { if (error.name === "AbortError") { throw new Error(`API 타임아웃: 30초 초과`); } throw new Error(`${endpoint} 실패: ${error.message}`); } throw error; } } // ============================================================================ // 서버 액션: 문서 리스트 조회 // ============================================================================ /** * 문서 리스트 조회 (GetVDRDocumentList) * @param filter 조회 필터 */ export async function fetchGetVDRDocumentList( filter: GetVDRDocumentListFilter ): Promise { // doc_gb 기본값 설정 const body = { proj_no: filter.proj_no, doc_gb: filter.doc_gb || "V", // 기본값 V ctgry: filter.ctgry || "", pkgNo: filter.pkgNo || "", vndrCd: filter.vndrCd || "", pic_deptcd: filter.pic_deptcd || "", doc_type: filter.doc_type || "", displn: filter.displn || "", mat_cd: filter.mat_cd || "", proj_nm: filter.proj_nm || "", stage: filter.stage || "", own_doc_no: filter.own_doc_no || "", doc_title: filter.doc_title || "", lang_gb: filter.lang_gb || "", }; return callSwpApi( "GetVDRDocumentList", body, "GetVDRDocumentListResult" ); } // ============================================================================ // 서버 액션: 첨부파일 리스트 조회 // ============================================================================ /** * 첨부파일 리스트 조회 (GetExternalInboxList) * @param filter 조회 필터 */ export async function fetchGetExternalInboxList( filter: GetExternalInboxListFilter ): Promise { const body = { projNo: filter.projNo, pkgNo: filter.pkgNo || "", vndrCd: filter.vndrCd || "", stage: filter.stage || "", owndocno: filter.owndocno || "", doctitle: filter.doctitle || "", }; return callSwpApi( "GetExternalInboxList", body, "GetExternalInboxListResult" ); } // ============================================================================ // 서버 액션: 프로젝트 데이터 일괄 조회 // ============================================================================ /** * 프로젝트의 문서 + 파일 리스트 동시 조회 * @param projectNo 프로젝트 번호 (예: "SN2190") * @param docGb 문서 구분 (M=MDR, V=VDR, 기본값: V) */ export async function fetchSwpProjectData( projectNo: string, docGb: "M" | "V" = "V" ): Promise<{ documents: SwpDocumentApiResponse[]; files: SwpFileApiResponse[]; }> { console.log(`[SWP API] 프로젝트 ${projectNo} 데이터 조회 시작`); const startTime = Date.now(); try { // 병렬 호출 const [documents, files] = await Promise.all([ fetchGetVDRDocumentList({ proj_no: projectNo, doc_gb: docGb, }), fetchGetExternalInboxList({ projNo: projectNo, }), ]); const duration = Date.now() - startTime; console.log( `[SWP API] 조회 완료: 문서 ${documents.length}개, 파일 ${files.length}개 (${duration}ms)` ); return { documents, files }; } catch (error) { console.error(`[SWP API] 조회 실패:`, error); throw error; } } // ============================================================================ // 파일 다운로드 URL 생성 // ============================================================================ /** * SWP 파일 다운로드 URL 생성 */ export async function getSwpFileDownloadUrl(file: { FLD_PATH: string; FILE_NM: string; }): Promise { // FLD_PATH: "\SN2190\C00035\\20170217180135" // FILE_NM: "C168-SH-SBN08-XG-20118-01_04_IFC_20170216.pdf" const encodedPath = encodeURIComponent(file.FLD_PATH); const encodedName = encodeURIComponent(file.FILE_NM); return `${SWP_BASE_URL}/Files/${encodedPath}/${encodedName}`; } /** * SWP 파일 직접 다운로드 (Blob) */ export async function downloadSwpFile(file: { FLD_PATH: string; FILE_NM: string; }): Promise { const url = await getSwpFileDownloadUrl(file); const response = await fetch(url, { method: "GET", signal: AbortSignal.timeout(60000), // 1분 }); if (!response.ok) { throw new Error(`파일 다운로드 실패: ${response.status}`); } return response.blob(); } // ============================================================================ // 통계 및 유틸리티 // ============================================================================ /** * API 응답 통계 */ export interface SwpDataStats { projectNo: string; documentCount: number; fileCount: number; revisionCount: number; avgFilesPerDoc: number; stages: Record; fileTypes: Record; totalFileSize: number; } export async function analyzeSwpData( projectNo: string, documents: SwpDocumentApiResponse[], files: SwpFileApiResponse[] ): Promise { // 리비전 카운트 const revisionSet = new Set(); files.forEach((f) => revisionSet.add(`${f.OWN_DOC_NO}|${f.REV_NO}`)); // 스테이지별 카운트 const stages: Record = {}; files.forEach((f) => { stages[f.STAGE] = (stages[f.STAGE] || 0) + 1; }); // 파일 타입별 카운트 const fileTypes: Record = {}; files.forEach((f) => { const ext = f.FILE_NM.split(".").pop()?.toLowerCase() || "unknown"; fileTypes[ext] = (fileTypes[ext] || 0) + 1; }); // 총 파일 사이즈 (숫자로 변환 가능한 것만) const totalFileSize = files.reduce((sum, f) => { const size = parseInt(f.FILE_SZ, 10); return sum + (isNaN(size) ? 0 : size); }, 0); return { projectNo, documentCount: documents.length, fileCount: files.length, revisionCount: revisionSet.size, avgFilesPerDoc: documents.length > 0 ? files.length / documents.length : 0, stages, fileTypes, totalFileSize, }; }