summaryrefslogtreecommitdiff
path: root/lib/swp/vendor-actions.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/swp/vendor-actions.ts')
-rw-r--r--lib/swp/vendor-actions.ts513
1 files changed, 145 insertions, 368 deletions
diff --git a/lib/swp/vendor-actions.ts b/lib/swp/vendor-actions.ts
index c96cf055..f65ed007 100644
--- a/lib/swp/vendor-actions.ts
+++ b/lib/swp/vendor-actions.ts
@@ -6,13 +6,16 @@ import db from "@/db/db";
import { vendors } from "@/db/schema/vendors";
import { contracts } from "@/db/schema/contract";
import { projects } from "@/db/schema/projects";
-import { swpDocumentRevisions, swpDocumentFiles } from "@/db/schema/SWP/swp-documents";
-import { eq, sql, and } from "drizzle-orm";
-import { fetchSwpDocuments, type SwpTableParams } from "./actions";
-import { fetchGetExternalInboxList } from "./api-client";
-import type { SwpFileApiResponse } from "./sync-service";
-import fs from "fs/promises";
-import path from "path";
+import { eq } from "drizzle-orm";
+import {
+ getDocumentList,
+ getDocumentDetail,
+ cancelStandbyFile,
+ downloadDocumentFile,
+ type DocumentListItem,
+ type DocumentDetail,
+ type DownloadFileResult
+} from "./document-service";
import { debugLog, debugError, debugSuccess, debugProcess, debugWarn } from "@/lib/debug-utils";
// ============================================================================
@@ -110,11 +113,11 @@ export async function fetchVendorProjects() {
}
// ============================================================================
-// 벤더 필터링된 문서 목록 조회
+// 벤더 필터링된 문서 목록 조회 (Full API 기반)
// ============================================================================
-export async function fetchVendorDocuments(params: SwpTableParams) {
- debugProcess("벤더 문서 목록 조회 시작", { page: params.page, pageSize: params.pageSize });
+export async function fetchVendorDocuments(projNo?: string): Promise<DocumentListItem[]> {
+ debugProcess("벤더 문서 목록 조회 시작", { projNo });
try {
const vendorInfo = await getVendorSessionInfo();
@@ -124,22 +127,21 @@ export async function fetchVendorDocuments(params: SwpTableParams) {
throw new Error("벤더 정보를 찾을 수 없습니다.");
}
- // 벤더 코드를 필터에 자동 추가
- const vendorParams: SwpTableParams = {
- ...params,
- filters: {
- ...params.filters,
- vndrCd: vendorInfo.vendorCode, // 벤더 코드 필터 강제 적용
- },
- };
+ if (!projNo) {
+ debugWarn("프로젝트 번호 없음");
+ return [];
+ }
- debugLog("SWP 문서 조회 호출", { vendorCode: vendorInfo.vendorCode, filters: vendorParams.filters });
+ debugLog("문서 목록 조회 시작", {
+ projNo,
+ vendorCode: vendorInfo.vendorCode
+ });
- // 기존 fetchSwpDocuments 재사용
- const result = await fetchSwpDocuments(vendorParams);
+ // document-service의 getDocumentList 사용
+ const documents = await getDocumentList(projNo, vendorInfo.vendorCode);
- debugSuccess("문서 목록 조회 성공", { total: result.total, dataCount: result.data.length });
- return result;
+ debugSuccess("문서 목록 조회 성공", { count: documents.length });
+ return documents;
} catch (error) {
debugError("문서 목록 조회 실패", error);
console.error("[fetchVendorDocuments] 오류:", error);
@@ -148,104 +150,114 @@ export async function fetchVendorDocuments(params: SwpTableParams) {
}
// ============================================================================
-// 파일 업로드
+// 문서 상세 조회 (Rev-Activity-File 트리)
// ============================================================================
-export interface FileUploadParams {
- revisionId: number;
- file: {
- FILE_NM: string;
- FILE_SEQ: string;
- FILE_SZ: string;
- FLD_PATH: string;
- STAT?: string;
- STAT_NM?: string;
- };
- fileBuffer?: Buffer; // 실제 파일 데이터 추가
-}
-
-export async function uploadFileToRevision(params: FileUploadParams) {
- debugProcess("파일 업로드 시작", { revisionId: params.revisionId, fileName: params.file.FILE_NM });
+export async function fetchVendorDocumentDetail(
+ projNo: string,
+ docNo: string
+): Promise<DocumentDetail> {
+ debugProcess("벤더 문서 상세 조회 시작", { projNo, docNo });
try {
const vendorInfo = await getVendorSessionInfo();
if (!vendorInfo) {
- debugError("벤더 정보 없음 - 파일 업로드 실패");
+ debugError("벤더 정보 없음");
throw new Error("벤더 정보를 찾을 수 없습니다.");
}
- const { revisionId } = params;
- debugLog("리비전 권한 확인 시작", { revisionId });
-
- // 1. 해당 리비전이 벤더에게 제공된 문서인지 확인
- const revisionCheck = await db
- .select({
- DOC_NO: swpDocumentRevisions.DOC_NO,
- VNDR_CD: sql<string>`(
- SELECT d."VNDR_CD"
- FROM swp.swp_documents d
- WHERE d."DOC_NO" = ${swpDocumentRevisions.DOC_NO}
- )`,
- })
- .from(swpDocumentRevisions)
- .where(eq(swpDocumentRevisions.id, revisionId))
- .limit(1);
+ debugLog("문서 상세 조회 시작", { projNo, docNo });
- debugLog("리비전 조회 결과", { found: !!revisionCheck[0], docNo: revisionCheck[0]?.DOC_NO });
+ // document-service의 getDocumentDetail 사용
+ const detail = await getDocumentDetail(projNo, docNo);
- if (!revisionCheck[0]) {
- debugError("리비전 없음", { revisionId });
- throw new Error("리비전을 찾을 수 없습니다.");
- }
+ debugSuccess("문서 상세 조회 성공", {
+ docNo: detail.docNo,
+ revisions: detail.revisions.length,
+ });
- // 벤더 코드가 일치하는지 확인
- if (revisionCheck[0].VNDR_CD !== vendorInfo.vendorCode) {
- debugError("권한 없음", {
- expected: vendorInfo.vendorCode,
- actual: revisionCheck[0].VNDR_CD,
- docNo: revisionCheck[0].DOC_NO
- });
- throw new Error("이 문서에 대한 권한이 없습니다.");
+ return detail;
+ } catch (error) {
+ debugError("문서 상세 조회 실패", error);
+ console.error("[fetchVendorDocumentDetail] 오류:", error);
+ throw new Error("문서 상세 조회 실패");
+ }
+}
+
+// ============================================================================
+// 파일 취소
+// ============================================================================
+
+export async function cancelVendorFile(
+ boxSeq: string,
+ actvSeq: string
+): Promise<void> {
+ debugProcess("벤더 파일 취소 시작", { boxSeq, actvSeq });
+
+ try {
+ const vendorInfo = await getVendorSessionInfo();
+
+ if (!vendorInfo) {
+ debugError("벤더 정보 없음");
+ throw new Error("벤더 정보를 찾을 수 없습니다.");
}
- debugSuccess("리비전 권한 확인 성공");
+ // vendorId를 문자열로 변환하여 사용
+ await cancelStandbyFile(boxSeq, actvSeq, String(vendorInfo.vendorId));
- const { revisionId: revId, file, fileBuffer } = params;
+ debugSuccess("파일 취소 완료", { boxSeq, actvSeq });
+ } catch (error) {
+ debugError("파일 취소 실패", error);
+ console.error("[cancelVendorFile] 오류:", error);
+ throw new Error("파일 취소 실패");
+ }
+}
- // 1. SWP 마운트 경로에 파일 저장
- debugProcess("파일 저장 단계 시작");
- await saveFileToSwpNetwork(revId, {
- FILE_NM: file.FILE_NM,
- fileBuffer: fileBuffer,
- });
+// ============================================================================
+// 파일 다운로드
+// ============================================================================
+
+export async function downloadVendorFile(
+ projNo: string,
+ ownDocNo: string,
+ fileName: string
+): Promise<DownloadFileResult> {
+ debugProcess("벤더 파일 다운로드 시작", { projNo, ownDocNo, fileName });
- // 2. 파일 저장 API 호출 (메타데이터 전송)
- debugProcess("API 호출 단계 시작");
- await callSwpFileSaveApi(revId, file);
+ try {
+ const vendorInfo = await getVendorSessionInfo();
+
+ if (!vendorInfo) {
+ debugError("벤더 정보 없음");
+ return {
+ success: false,
+ error: "벤더 정보를 찾을 수 없습니다.",
+ };
+ }
- // 3. 파일 리스트 API 호출 (업데이트된 파일 목록 조회)
- debugProcess("파일 목록 조회 단계 시작");
- const updatedFiles = await fetchUpdatedFileList(revId);
- debugLog("업데이트된 파일 목록", { count: updatedFiles.length });
+ // document-service의 downloadDocumentFile 사용
+ const result = await downloadDocumentFile(projNo, ownDocNo, fileName);
- // 4. 파일 목록 DB 동기화 (새 파일들 추가)
- debugProcess("DB 동기화 단계 시작");
- await syncSwpDocumentFiles(revId, updatedFiles);
+ if (result.success) {
+ debugSuccess("파일 다운로드 완료", { fileName });
+ } else {
+ debugWarn("파일 다운로드 실패", { fileName, error: result.error });
+ }
- debugSuccess("파일 업로드 완료", { fileName: file.FILE_NM, revisionId });
- return { success: true, fileId: 0, action: "uploaded" };
+ return result;
} catch (error) {
- debugError("파일 업로드 실패", error);
- console.error("[uploadFileToRevision] 오류:", error);
- throw new Error(
- error instanceof Error ? error.message : "파일 업로드 실패"
- );
+ debugError("파일 다운로드 실패", error);
+ console.error("[downloadVendorFile] 오류:", error);
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : "파일 다운로드 실패",
+ };
}
}
// ============================================================================
-// 벤더 통계 조회
+// 벤더 통계 조회 (Full API 기반)
// ============================================================================
export async function fetchVendorSwpStats(projNo?: string) {
@@ -259,48 +271,52 @@ export async function fetchVendorSwpStats(projNo?: string) {
throw new Error("벤더 정보를 찾을 수 없습니다.");
}
- const whereConditions = [
- sql`d."VNDR_CD" = ${vendorInfo.vendorCode}`,
- ];
+ if (!projNo) {
+ debugWarn("프로젝트 번호 없음");
+ return {
+ total_documents: 0,
+ total_revisions: 0,
+ total_files: 0,
+ uploaded_files: 0,
+ last_sync: null,
+ };
+ }
- if (projNo) {
- whereConditions.push(sql`d."PROJ_NO" = ${projNo}`);
+ // API에서 문서 목록 조회
+ const documents = await getDocumentList(projNo, vendorInfo.vendorCode);
+
+ // 통계 계산
+ let totalRevisions = 0;
+ let totalFiles = 0;
+ let uploadedFiles = 0;
+
+ for (const doc of documents) {
+ totalFiles += doc.fileCount;
+ // standbyFileCount가 0이 아니면 업로드된 것으로 간주
+ uploadedFiles += doc.fileCount - doc.standbyFileCount;
+
+ // 리비전 수 추정 (LTST_REV_NO 기반)
+ if (doc.LTST_REV_NO) {
+ const revNum = parseInt(doc.LTST_REV_NO, 10);
+ if (!isNaN(revNum)) {
+ totalRevisions += revNum + 1; // Rev 00부터 시작이므로 +1
+ }
+ }
}
- debugLog("통계 SQL 실행", { vendorCode: vendorInfo.vendorCode, projNo });
-
- const stats = await db.execute<{
- total_documents: number;
- total_revisions: number;
- total_files: number;
- uploaded_files: number;
- last_sync: Date | null;
- }>(sql`
- SELECT
- COUNT(DISTINCT d."DOC_NO")::int as total_documents,
- COUNT(DISTINCT r.id)::int as total_revisions,
- COUNT(f.id)::int as total_files,
- COUNT(CASE WHEN f."FLD_PATH" IS NOT NULL AND f."FLD_PATH" != '' THEN 1 END)::int as uploaded_files,
- MAX(d.last_synced_at) as last_sync
- FROM swp.swp_documents d
- LEFT JOIN swp.swp_document_revisions r ON d."DOC_NO" = r."DOC_NO"
- LEFT JOIN swp.swp_document_files f ON r.id = f.revision_id
- WHERE ${sql.join(whereConditions, sql` AND `)}
- `);
-
- const result = stats.rows[0] || {
- total_documents: 0,
- total_revisions: 0,
- total_files: 0,
- uploaded_files: 0,
- last_sync: null,
+ const result = {
+ total_documents: documents.length,
+ total_revisions: totalRevisions,
+ total_files: totalFiles,
+ uploaded_files: uploadedFiles,
+ last_sync: new Date(), // API 기반이므로 항상 최신
};
debugSuccess("통계 조회 성공", {
documents: result.total_documents,
revisions: result.total_revisions,
files: result.total_files,
- uploaded: result.uploaded_files
+ uploaded: result.uploaded_files,
});
return result;
@@ -318,245 +334,6 @@ export async function fetchVendorSwpStats(projNo?: string) {
}
// ============================================================================
-// SWP 파일 업로드 헬퍼 함수들
+// 주의: 파일 업로드는 /api/swp/upload 라우트에서 처리됩니다
// ============================================================================
-/**
- * 1. SWP 마운트 경로에 파일 저장
- */
-async function saveFileToSwpNetwork(
- revisionId: number,
- fileInfo: { FILE_NM: string; fileBuffer?: Buffer }
-): Promise<string> {
- debugProcess("네트워크 파일 저장 시작", { revisionId, fileName: fileInfo.FILE_NM });
-
- // 리비전 정보 조회
- const revisionInfo = await db
- .select({
- DOC_NO: swpDocumentRevisions.DOC_NO,
- REV_NO: swpDocumentRevisions.REV_NO,
- PROJ_NO: sql<string>`(
- SELECT d."PROJ_NO" FROM swp.swp_documents d
- WHERE d."DOC_NO" = ${swpDocumentRevisions.DOC_NO}
- )`,
- VNDR_CD: sql<string>`(
- SELECT d."VNDR_CD" FROM swp.swp_documents d
- WHERE d."DOC_NO" = ${swpDocumentRevisions.DOC_NO}
- )`,
- })
- .from(swpDocumentRevisions)
- .where(eq(swpDocumentRevisions.id, revisionId))
- .limit(1);
-
- debugLog("리비전 정보 조회", { found: !!revisionInfo[0], docNo: revisionInfo[0]?.DOC_NO });
-
- if (!revisionInfo[0]) {
- debugError("리비전 정보 없음");
- throw new Error("리비전 정보를 찾을 수 없습니다");
- }
-
- const { PROJ_NO, VNDR_CD, DOC_NO, REV_NO } = revisionInfo[0];
-
- // SWP 마운트 경로 생성
- const mountDir = process.env.SWP_MOUNT_DIR || "/mnt/swp-smb-dir";
- const targetDir = path.join(mountDir, PROJ_NO, VNDR_CD, DOC_NO, REV_NO);
-
- debugLog("파일 저장 경로 생성", { mountDir, targetDir });
-
- // 디렉토리 생성
- await fs.mkdir(targetDir, { recursive: true });
- debugLog("디렉토리 생성 완료");
-
- // 파일 저장
- const targetPath = path.join(targetDir, fileInfo.FILE_NM);
-
- if (fileInfo.fileBuffer) {
- await fs.writeFile(targetPath, fileInfo.fileBuffer);
- debugSuccess("파일 저장 완료", { fileName: fileInfo.FILE_NM, targetPath, size: fileInfo.fileBuffer.length });
- } else {
- debugWarn("파일 버퍼 없음", { fileName: fileInfo.FILE_NM });
- }
-
- return targetPath;
-}
-
-/**
- * 2. 파일 저장 API 호출 (메타데이터 전송)
- */
-async function callSwpFileSaveApi(
- revisionId: number,
- fileInfo: FileUploadParams['file']
-): Promise<void> {
- debugProcess("SWP 파일 저장 API 호출 시작", { revisionId, fileName: fileInfo.FILE_NM });
-
- // TODO: SWP 파일 저장 API 구현
- // buyer-system의 sendToInBox 패턴 참고
- debugLog("메타데이터 전송", {
- fileName: fileInfo.FILE_NM,
- fileSeq: fileInfo.FILE_SEQ,
- filePath: fileInfo.FLD_PATH
- });
-
- // 임시 구현: 실제로는 SWP SaveFile API 등을 호출해야 함
- // 예: SaveFile, UploadFile API 등
- debugWarn("SWP 파일 저장 API가 아직 구현되지 않음 - 임시 스킵");
-}
-
-/**
- * 3. 파일 리스트 API 호출 (업데이트된 파일 목록 조회)
- */
-async function fetchUpdatedFileList(revisionId: number): Promise<SwpFileApiResponse[]> {
- debugProcess("업데이트된 파일 목록 조회 시작", { revisionId });
-
- // 리비전 정보 조회
- const revisionInfo = await db
- .select({
- DOC_NO: swpDocumentRevisions.DOC_NO,
- PROJ_NO: sql<string>`(
- SELECT d."PROJ_NO" FROM swp.swp_documents d
- WHERE d."DOC_NO" = ${swpDocumentRevisions.DOC_NO}
- )`,
- VNDR_CD: sql<string>`(
- SELECT d."VNDR_CD" FROM swp.swp_documents d
- WHERE d."DOC_NO" = ${swpDocumentRevisions.DOC_NO}
- )`,
- })
- .from(swpDocumentRevisions)
- .where(eq(swpDocumentRevisions.id, revisionId))
- .limit(1);
-
- debugLog("리비전 정보 조회", { found: !!revisionInfo[0], docNo: revisionInfo[0]?.DOC_NO });
-
- if (!revisionInfo[0]) {
- debugError("리비전 정보 없음");
- throw new Error("리비전 정보를 찾을 수 없습니다");
- }
-
- const { PROJ_NO, VNDR_CD } = revisionInfo[0];
-
- debugLog("SWP 파일 목록 API 호출", { projNo: PROJ_NO, vndrCd: VNDR_CD });
-
- // SWP API에서 업데이트된 파일 목록 조회
- const files = await fetchGetExternalInboxList({
- projNo: PROJ_NO,
- vndrCd: VNDR_CD,
- });
-
- debugSuccess("파일 목록 조회 완료", { count: files.length });
- return files;
-}
-
-/**
- * 4. 파일 목록 DB 동기화 (새 파일들 추가)
- */
-async function syncSwpDocumentFiles(
- revisionId: number,
- apiFiles: SwpFileApiResponse[]
-): Promise<void> {
- debugProcess("DB 동기화 시작", { revisionId, fileCount: apiFiles.length });
-
- // 리비전 정보에서 DOC_NO 가져오기
- const revisionInfo = await db
- .select({
- DOC_NO: swpDocumentRevisions.DOC_NO,
- })
- .from(swpDocumentRevisions)
- .where(eq(swpDocumentRevisions.id, revisionId))
- .limit(1);
-
- debugLog("리비전 DOC_NO 조회", { found: !!revisionInfo[0], docNo: revisionInfo[0]?.DOC_NO });
-
- if (!revisionInfo[0]) {
- debugError("리비전 정보 없음");
- throw new Error("리비전 정보를 찾을 수 없습니다");
- }
-
- const { DOC_NO } = revisionInfo[0];
- let processedCount = 0;
- let updatedCount = 0;
- let insertedCount = 0;
-
- for (const apiFile of apiFiles) {
- try {
- processedCount++;
-
- // 기존 파일 확인
- const existingFile = await db
- .select({ id: swpDocumentFiles.id })
- .from(swpDocumentFiles)
- .where(
- and(
- eq(swpDocumentFiles.revision_id, revisionId),
- eq(swpDocumentFiles.FILE_SEQ, apiFile.FILE_SEQ || "1")
- )
- )
- .limit(1);
-
- const fileData = {
- DOC_NO: DOC_NO,
- FILE_NM: apiFile.FILE_NM,
- FILE_SEQ: apiFile.FILE_SEQ || "1",
- FILE_SZ: apiFile.FILE_SZ || "0",
- FLD_PATH: apiFile.FLD_PATH,
- STAT: apiFile.STAT || null,
- STAT_NM: apiFile.STAT_NM || null,
- ACTV_NO: apiFile.ACTV_NO || null,
- IDX: apiFile.IDX || null,
- CRTER: apiFile.CRTER || null,
- CRTE_DTM: apiFile.CRTE_DTM || null,
- CHGR: apiFile.CHGR || null,
- CHG_DTM: apiFile.CHG_DTM || null,
- sync_status: 'synced' as const,
- last_synced_at: new Date(),
- updated_at: new Date(),
- };
-
- if (existingFile[0]) {
- // 기존 파일 업데이트
- await db
- .update(swpDocumentFiles)
- .set({
- ...fileData,
- updated_at: new Date(),
- })
- .where(eq(swpDocumentFiles.id, existingFile[0].id));
- updatedCount++;
- debugLog("파일 업데이트", { fileName: apiFile.FILE_NM, fileSeq: apiFile.FILE_SEQ });
- } else {
- // 새 파일 추가
- await db.insert(swpDocumentFiles).values({
- revision_id: revisionId,
- DOC_NO: DOC_NO,
- FILE_NM: apiFile.FILE_NM,
- FILE_SEQ: apiFile.FILE_SEQ || "1",
- FILE_SZ: apiFile.FILE_SZ || "0",
- FLD_PATH: apiFile.FLD_PATH,
- STAT: apiFile.STAT || null,
- STAT_NM: apiFile.STAT_NM || null,
- ACTV_NO: apiFile.ACTV_NO || null,
- IDX: apiFile.IDX || null,
- CRTER: apiFile.CRTER || null,
- CRTE_DTM: apiFile.CRTE_DTM || null,
- CHGR: apiFile.CHGR || null,
- CHG_DTM: apiFile.CHG_DTM || null,
- sync_status: 'synced' as const,
- last_synced_at: new Date(),
- updated_at: new Date(),
- });
- insertedCount++;
- debugLog("파일 추가", { fileName: apiFile.FILE_NM, fileSeq: apiFile.FILE_SEQ });
- }
- } catch (error) {
- debugError("파일 동기화 실패", { fileName: apiFile.FILE_NM, error });
- console.error(`파일 동기화 실패: ${apiFile.FILE_NM}`, error);
- // 개별 파일 실패는 전체 프로세스를 중단하지 않음
- }
- }
-
- debugSuccess("DB 동기화 완료", {
- processed: processedCount,
- updated: updatedCount,
- inserted: insertedCount
- });
-}
-