diff options
Diffstat (limited to 'lib/swp/api-client.ts')
| -rw-r--r-- | lib/swp/api-client.ts | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/lib/swp/api-client.ts b/lib/swp/api-client.ts new file mode 100644 index 00000000..9ce8c5c1 --- /dev/null +++ b/lib/swp/api-client.ts @@ -0,0 +1,304 @@ +"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<T>( + endpoint: string, + body: Record<string, unknown>, + resultKey: string +): Promise<T[]> { + 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<SwpDocumentApiResponse[]> { + // 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<SwpDocumentApiResponse>( + "GetVDRDocumentList", + body, + "GetVDRDocumentListResult" + ); +} + +// ============================================================================ +// 서버 액션: 첨부파일 리스트 조회 +// ============================================================================ + +/** + * 첨부파일 리스트 조회 (GetExternalInboxList) + * @param filter 조회 필터 + */ +export async function fetchGetExternalInboxList( + filter: GetExternalInboxListFilter +): Promise<SwpFileApiResponse[]> { + const body = { + projNo: filter.projNo, + pkgNo: filter.pkgNo || "", + vndrCd: filter.vndrCd || "", + stage: filter.stage || "", + owndocno: filter.owndocno || "", + doctitle: filter.doctitle || "", + }; + + return callSwpApi<SwpFileApiResponse>( + "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<string> { + // 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<Blob> { + 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<string, number>; + fileTypes: Record<string, number>; + totalFileSize: number; +} + +export async function analyzeSwpData( + projectNo: string, + documents: SwpDocumentApiResponse[], + files: SwpFileApiResponse[] +): Promise<SwpDataStats> { + // 리비전 카운트 + const revisionSet = new Set<string>(); + files.forEach((f) => revisionSet.add(`${f.OWN_DOC_NO}|${f.REV_NO}`)); + + // 스테이지별 카운트 + const stages: Record<string, number> = {}; + files.forEach((f) => { + stages[f.STAGE] = (stages[f.STAGE] || 0) + 1; + }); + + // 파일 타입별 카운트 + const fileTypes: Record<string, number> = {}; + 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, + }; +} + |
