diff options
| author | joonhoekim <26rote@gmail.com> | 2025-10-31 15:36:41 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-10-31 15:36:41 +0900 |
| commit | 6a7648d175522b705d1b5c1ccaef01bfaab8cd1e (patch) | |
| tree | 1f86b850b4d3eb2f4732f86a0ecaa7a6379b1d1a /app/api/swp/upload | |
| parent | 63be7ba1e28c06ccf3ae8c40df7cbe575c28f4b6 (diff) | |
(김준회) SWP 문서 업로드/다운로드 Full API 방식 대응 변경
Diffstat (limited to 'app/api/swp/upload')
| -rw-r--r-- | app/api/swp/upload/route.ts | 138 |
1 files changed, 72 insertions, 66 deletions
diff --git a/app/api/swp/upload/route.ts b/app/api/swp/upload/route.ts index b38c4ff4..3e15e0a3 100644 --- a/app/api/swp/upload/route.ts +++ b/app/api/swp/upload/route.ts @@ -1,11 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import * as fs from "fs/promises"; import * as path from "path"; -import { eq, and } from "drizzle-orm"; -import db from "@/db/db"; -import { swpDocuments } from "@/db/schema/SWP/swp-documents"; import { fetchGetVDRDocumentList, fetchGetExternalInboxList } from "@/lib/swp/api-client"; -import { syncSwpProject } from "@/lib/swp/sync-service"; import { debugLog, debugError, debugSuccess } from "@/lib/debug-utils"; // API Route 설정 @@ -26,21 +22,27 @@ interface InBoxFileInfo { } /** - * 파일명 파싱: [DOC_NO]_[REV_NO]_[STAGE]_[자유-파일명].[확장자] - * 자유 파일명에는 언더스코어가 포함될 수 있음 + * 파일명 파싱: [DOC_NO]_[REV_NO]_[STAGE].[확장자] 또는 [DOC_NO]_[REV_NO]_[STAGE]_[자유-파일명].[확장자] + * 자유 파일명은 선택사항이며, 포함될 경우 언더스코어를 포함할 수 있음 */ function parseFileName(fileName: string) { const lastDotIndex = fileName.lastIndexOf("."); - const extension = lastDotIndex !== -1 ? fileName.substring(lastDotIndex + 1) : ""; - const nameWithoutExt = lastDotIndex !== -1 ? fileName.substring(0, lastDotIndex) : fileName; + + // 확장자 검증 + if (lastDotIndex === -1) { + throw new Error(`파일 확장자가 없습니다: ${fileName}`); + } + + const extension = fileName.substring(lastDotIndex + 1); + const nameWithoutExt = fileName.substring(0, lastDotIndex); const parts = nameWithoutExt.split("_"); - // 최소 4개 파트 필요: docNo, revNo, stage, fileName - if (parts.length < 4) { + // 최소 3개 파트 필요: docNo, revNo, stage (fileName은 선택사항) + if (parts.length < 3) { throw new Error( `잘못된 파일명 형식입니다: ${fileName}. ` + - `형식: [DOC_NO]_[REV_NO]_[STAGE]_[파일명].확장자 (언더스코어 최소 3개 필요)` + `형식: [DOC_NO]_[REV_NO]_[STAGE].[확장자] (언더스코어 최소 2개 필요)` ); } @@ -49,10 +51,29 @@ function parseFileName(fileName: string) { const revNo = parts[1]; const stage = parts[2]; - // 나머지는 자유 파일명 (언더스코어 포함 가능) - const customFileName = parts.slice(3).join("_"); + // 나머지는 자유 파일명 (선택사항, 언더스코어 포함 가능) + const customFileName = parts.length > 3 ? parts.slice(3).join("_") : ""; + + // 필수 항목이 비어있지 않은지 확인 + if (!ownDocNo || ownDocNo.trim() === "") { + throw new Error(`문서번호(DOC_NO)가 비어있습니다: ${fileName}`); + } + + if (!revNo || revNo.trim() === "") { + throw new Error(`리비전 번호(REV_NO)가 비어있습니다: ${fileName}`); + } + + if (!stage || stage.trim() === "") { + throw new Error(`스테이지(STAGE)가 비어있습니다: ${fileName}`); + } - return { ownDocNo, revNo, stage, fileName: customFileName, extension }; + return { + ownDocNo: ownDocNo.trim(), + revNo: revNo.trim(), + stage: stage.trim(), + fileName: customFileName.trim(), + extension + }; } /** @@ -71,22 +92,43 @@ function generateTimestamp(): string { } /** - * CPY_CD 조회 + * CPY_CD 조회 (API 기반) + * GetVDRDocumentList API를 호출하여 해당 프로젝트/벤더의 CPY_CD를 조회 */ async function getCpyCdForVendor(projNo: string, vndrCd: string): Promise<string> { - const result = await db - .select({ CPY_CD: swpDocuments.CPY_CD }) - .from(swpDocuments) - .where(and(eq(swpDocuments.PROJ_NO, projNo), eq(swpDocuments.VNDR_CD, vndrCd))) - .limit(1); + try { + console.log(`[getCpyCdForVendor] API 조회 시작: projNo=${projNo}, vndrCd=${vndrCd}`); + + // GetVDRDocumentList API 호출 (벤더 필터 적용) + const documents = await fetchGetVDRDocumentList({ + proj_no: projNo, + doc_gb: "V", + vndrCd: vndrCd, + }); - if (!result || result.length === 0 || !result[0].CPY_CD) { - throw new Error( - `프로젝트 ${projNo}에서 벤더 코드 ${vndrCd}에 해당하는 회사 코드(CPY_CD)를 찾을 수 없습니다.` - ); - } + console.log(`[getCpyCdForVendor] API 조회 완료: ${documents.length}개 문서`); - return result[0].CPY_CD; + if (!documents || documents.length === 0) { + throw new Error( + `프로젝트 ${projNo}에서 벤더 코드 ${vndrCd}에 할당된 문서가 없습니다.` + ); + } + + // 첫 번째 문서에서 CPY_CD 추출 + const cpyCd = documents[0].CPY_CD; + + if (!cpyCd) { + throw new Error( + `프로젝트 ${projNo}에서 벤더 코드 ${vndrCd}에 해당하는 회사 코드(CPY_CD)를 찾을 수 없습니다.` + ); + } + + console.log(`[getCpyCdForVendor] CPY_CD 확인: ${cpyCd}`); + return cpyCd; + } catch (error) { + console.error("[getCpyCdForVendor] 오류:", error); + throw error; + } } /** @@ -308,39 +350,8 @@ export async function POST(request: NextRequest) { await callSaveInBoxList(inBoxFileInfos); } - // 업로드 성공 후 동기화 처리 (현재 벤더의 변경사항만) - if (result.successCount > 0) { - try { - console.log(`[upload] 동기화 시작: projNo=${projNo}, vndrCd=${vndrCd}`); - - // GetVDRDocumentList 및 GetExternalInboxList API 호출 (벤더 필터 적용) - const [documents, files] = await Promise.all([ - fetchGetVDRDocumentList({ - proj_no: projNo, - doc_gb: "V", - vndrCd: vndrCd, // 현재 벤더만 필터링 - }), - fetchGetExternalInboxList({ - projNo: projNo, - vndrCd: vndrCd, // 현재 벤더만 필터링 - }), - ]); - - console.log(`[upload] API 조회 완료: 문서 ${documents.length}개, 파일 ${files.length}개`); - - // 동기화 실행 - const syncResult = await syncSwpProject(projNo, documents, files); - - if (syncResult.success) { - console.log(`[upload] 동기화 완료:`, syncResult.stats); - } else { - console.warn(`[upload] 동기화 경고:`, syncResult.errors); - } - } catch (syncError) { - // 동기화 실패는 경고로만 처리 (업로드 자체는 성공) - console.error("[upload] 동기화 실패 (업로드는 성공):", syncError); - } - } + // ⚠️ Full API 방식으로 전환했으므로 로컬 DB 동기화는 불필요 + // 업로드 성공 시 SaveInBoxList API 호출만으로 충분 (이미 위에서 완료) // 결과 메시지 생성 let message: string; @@ -348,7 +359,7 @@ export async function POST(request: NextRequest) { if (result.failedCount === 0) { success = true; - message = `${result.successCount}개 파일이 성공적으로 업로드 및 동기화되었습니다.`; + message = `${result.successCount}개 파일이 성공적으로 업로드되었습니다.`; } else if (result.successCount === 0) { success = false; message = `모든 파일 업로드에 실패했습니다. (${result.failedCount}개)`; @@ -359,18 +370,13 @@ export async function POST(request: NextRequest) { console.log(`[upload] 완료:`, { success, message, result }); - // 동기화 완료 정보 추가 - const syncCompleted = result.successCount > 0; - const syncTimestamp = new Date().toISOString(); - return NextResponse.json({ success, message, successCount: result.successCount, failedCount: result.failedCount, details: result.details, - syncCompleted, - syncTimestamp, + uploadTimestamp: new Date().toISOString(), affectedVndrCd: vndrCd, }); } catch (error) { |
