diff options
| author | joonhoekim <26rote@gmail.com> | 2025-10-23 18:44:19 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-10-23 18:44:19 +0900 |
| commit | 04bd1965c3699a4b29ed9c9627574bfeedd3d6c6 (patch) | |
| tree | 691b9a6e844a788937a240d47e77e8cfa848a88a /lib/swp/vendor-actions.ts | |
| parent | 535e234dbd674bf2e5ecf344e03ed8ae5b2cbd6c (diff) | |
(김준회) SWP 문서 업로드 (Submisssion) 초기 개발건
Diffstat (limited to 'lib/swp/vendor-actions.ts')
| -rw-r--r-- | lib/swp/vendor-actions.ts | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/lib/swp/vendor-actions.ts b/lib/swp/vendor-actions.ts new file mode 100644 index 00000000..7d6dfa85 --- /dev/null +++ b/lib/swp/vendor-actions.ts @@ -0,0 +1,273 @@ +"use server"; + +import { getServerSession } from "next-auth"; +import { authOptions } from "@/app/api/auth/[...nextauth]/route"; +import db from "@/db/db"; +import { vendors } from "@/db/schema/vendors"; +import { contracts } from "@/db/schema/contract"; +import { projects } from "@/db/schema/projects"; +import { swpDocumentFiles, swpDocumentRevisions } from "@/db/schema/SWP/swp-documents"; +import { eq, and, sql } from "drizzle-orm"; +import { fetchSwpDocuments, type SwpTableParams } from "./actions"; + +// ============================================================================ +// 벤더 세션 정보 조회 +// ============================================================================ + +interface VendorSessionInfo { + vendorId: number; + vendorCode: string; + vendorName: string; + companyId: number; +} + +export async function getVendorSessionInfo(): Promise<VendorSessionInfo | null> { + const session = await getServerSession(authOptions); + + if (!session?.user?.companyId) { + return null; + } + + const companyId = typeof session.user.companyId === 'string' + ? parseInt(session.user.companyId, 10) + : session.user.companyId as number; + + // vendors 테이블에서 companyId로 벤더 정보 조회 + const vendor = await db + .select({ + id: vendors.id, + vendorCode: vendors.vendorCode, + vendorName: vendors.vendorName, + }) + .from(vendors) + .where(eq(vendors.id, companyId)) + .limit(1); + + if (!vendor[0] || !vendor[0].vendorCode) { + return null; + } + + return { + vendorId: vendor[0].id, + vendorCode: vendor[0].vendorCode, + vendorName: vendor[0].vendorName, + companyId, + }; +} + +// ============================================================================ +// 벤더의 프로젝트 목록 조회 +// ============================================================================ + +export async function fetchVendorProjects() { + try { + const vendorInfo = await getVendorSessionInfo(); + + if (!vendorInfo) { + throw new Error("벤더 정보를 찾을 수 없습니다."); + } + + // contracts 테이블에서 해당 벤더의 계약들의 프로젝트 조회 + const vendorProjects = await db + .selectDistinct({ + PROJ_NO: projects.code, + PROJ_NM: projects.name, + contract_count: sql<number>`COUNT(DISTINCT ${contracts.id})::int`, + }) + .from(contracts) + .innerJoin(projects, eq(contracts.projectId, projects.id)) + .where(eq(contracts.vendorId, vendorInfo.vendorId)) + .groupBy(projects.id, projects.code, projects.name) + .orderBy(sql`COUNT(DISTINCT ${contracts.id}) DESC`); + + return vendorProjects; + } catch (error) { + console.error("[fetchVendorProjects] 오류:", error); + return []; + } +} + +// ============================================================================ +// 벤더 필터링된 문서 목록 조회 +// ============================================================================ + +export async function fetchVendorDocuments(params: SwpTableParams) { + try { + const vendorInfo = await getVendorSessionInfo(); + + if (!vendorInfo) { + throw new Error("벤더 정보를 찾을 수 없습니다."); + } + + // 벤더 코드를 필터에 자동 추가 + const vendorParams: SwpTableParams = { + ...params, + filters: { + ...params.filters, + vndrCd: vendorInfo.vendorCode, // 벤더 코드 필터 강제 적용 + }, + }; + + // 기존 fetchSwpDocuments 재사용 + return await fetchSwpDocuments(vendorParams); + } catch (error) { + console.error("[fetchVendorDocuments] 오류:", error); + throw new Error("문서 목록 조회 실패"); + } +} + +// ============================================================================ +// 파일 업로드 +// ============================================================================ + +export interface FileUploadParams { + revisionId: number; + file: { + FILE_NM: string; + FILE_SEQ: string; + FILE_SZ: string; + FLD_PATH: string; + STAT?: string; + STAT_NM?: string; + }; +} + +export async function uploadFileToRevision(params: FileUploadParams) { + try { + const vendorInfo = await getVendorSessionInfo(); + + if (!vendorInfo) { + throw new Error("벤더 정보를 찾을 수 없습니다."); + } + + const { revisionId, file } = params; + + // 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); + + if (!revisionCheck[0]) { + throw new Error("리비전을 찾을 수 없습니다."); + } + + // 벤더 코드가 일치하는지 확인 + if (revisionCheck[0].VNDR_CD !== vendorInfo.vendorCode) { + throw new Error("이 문서에 대한 권한이 없습니다."); + } + + // 2. 파일 정보 저장 (upsert) + const existingFile = await db + .select({ id: swpDocumentFiles.id }) + .from(swpDocumentFiles) + .where( + and( + eq(swpDocumentFiles.revision_id, revisionId), + eq(swpDocumentFiles.FILE_SEQ, file.FILE_SEQ) + ) + ) + .limit(1); + + if (existingFile[0]) { + // 업데이트 + await db.execute(sql` + UPDATE swp.swp_document_files + SET + "FILE_NM" = ${file.FILE_NM}, + "FILE_SZ" = ${file.FILE_SZ}, + "FLD_PATH" = ${file.FLD_PATH}, + "STAT" = ${file.STAT || null}, + "STAT_NM" = ${file.STAT_NM || null}, + sync_status = 'synced', + updated_at = NOW() + WHERE id = ${existingFile[0].id} + `); + + return { success: true, fileId: existingFile[0].id, action: "updated" }; + } else { + // 삽입 + const result = await db.execute<{ id: number }>(sql` + INSERT INTO swp.swp_document_files + (revision_id, "FILE_NM", "FILE_SEQ", "FILE_SZ", "FLD_PATH", "STAT", "STAT_NM", sync_status) + VALUES + (${revisionId}, ${file.FILE_NM}, ${file.FILE_SEQ}, ${file.FILE_SZ}, ${file.FLD_PATH}, ${file.STAT || null}, ${file.STAT_NM || null}, 'synced') + RETURNING id + `); + + return { success: true, fileId: result.rows[0].id, action: "created" }; + } + } catch (error) { + console.error("[uploadFileToRevision] 오류:", error); + throw new Error( + error instanceof Error ? error.message : "파일 업로드 실패" + ); + } +} + +// ============================================================================ +// 벤더 통계 조회 +// ============================================================================ + +export async function fetchVendorSwpStats(projNo?: string) { + try { + const vendorInfo = await getVendorSessionInfo(); + + if (!vendorInfo) { + throw new Error("벤더 정보를 찾을 수 없습니다."); + } + + const whereConditions = [ + sql`d."VNDR_CD" = ${vendorInfo.vendorCode}`, + ]; + + if (projNo) { + whereConditions.push(sql`d."PROJ_NO" = ${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 `)} + `); + + return stats.rows[0] || { + total_documents: 0, + total_revisions: 0, + total_files: 0, + uploaded_files: 0, + last_sync: null, + }; + } catch (error) { + console.error("[fetchVendorSwpStats] 오류:", error); + return { + total_documents: 0, + total_revisions: 0, + total_files: 0, + uploaded_files: 0, + last_sync: null, + }; + } +} + |
