summaryrefslogtreecommitdiff
path: root/lib/swp/api-client.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/swp/api-client.ts')
-rw-r--r--lib/swp/api-client.ts304
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,
+ };
+}
+