summaryrefslogtreecommitdiff
path: root/app/api/revision-upload-ship/route.ts
diff options
context:
space:
mode:
Diffstat (limited to 'app/api/revision-upload-ship/route.ts')
-rw-r--r--app/api/revision-upload-ship/route.ts281
1 files changed, 281 insertions, 0 deletions
diff --git a/app/api/revision-upload-ship/route.ts b/app/api/revision-upload-ship/route.ts
new file mode 100644
index 00000000..c68d405e
--- /dev/null
+++ b/app/api/revision-upload-ship/route.ts
@@ -0,0 +1,281 @@
+import { NextRequest, NextResponse } from "next/server"
+import { writeFile } from "fs/promises"
+import { join } from "path"
+import { v4 as uuidv4 } from "uuid"
+import path from "path"
+import { revalidateTag } from "next/cache"
+
+import db from "@/db/db"
+import {
+ documents,
+ issueStages,
+ revisions,
+ documentAttachments,
+} from "@/db/schema/vendorDocu"
+import { and, eq } from "drizzle-orm"
+
+/* change log 유틸 */
+import {
+ logRevisionChange,
+ logAttachmentChange,
+} from "@/lib/vendor-document-list/sync-service"
+
+export async function POST(request: NextRequest) {
+ try {
+ const formData = await request.formData()
+
+ /* ------- 파라미터 파싱 ------- */
+ const usage = formData.get("usage") as string | null
+ const usageType = formData.get("usageType") as string | null
+ const revision = formData.get("revision") as string | null
+ const docId = Number(formData.get("documentId"))
+ const uploaderName = formData.get("uploaderName") as string | null
+ const comment = formData.get("comment") as string | null
+ const targetSystem = (formData.get("targetSystem") as string | null) ?? "DOLCE"
+ const attachmentFiles = formData.getAll("attachments") as File[]
+
+ /* ------- 검증 ------- */
+ if (!docId || Number.isNaN(docId))
+ return NextResponse.json({ error: "Invalid documentId" }, { status: 400 })
+ if (!usage || !revision)
+ return NextResponse.json({ error: "Missing usage or revision" }, { status: 400 })
+ if (!attachmentFiles.length)
+ return NextResponse.json({ error: "No files provided" }, { status: 400 })
+
+ const MAX = 50 * 1024 * 1024 // 50MB (다이얼로그 제한과 맞춤)
+ for (const f of attachmentFiles)
+ if (f.size > MAX)
+ return NextResponse.json(
+ { error: `${f.name} exceeds 50MB limit` },
+ { status: 400 }
+ )
+
+ /* ------- 계약 ID 확보 ------- */
+ const [docInfo] = await db
+ .select({ contractId: documents.contractId })
+ .from(documents)
+ .where(eq(documents.id, docId))
+ .limit(1)
+
+ if (!docInfo) {
+ return NextResponse.json({ error: "Document not found" }, { status: 404 })
+ }
+
+ /* ------- Stage 찾기 로직 ------- */
+ // 1. usage 값과 일치하는 stage 찾기
+ let targetStage = await db
+ .select({ id: issueStages.id, stageName: issueStages.stageName })
+ .from(issueStages)
+ .where(and(
+ eq(issueStages.documentId, docId),
+ eq(issueStages.stageName, usage)
+ ))
+ .limit(1)
+
+ // 2. 없으면 해당 문서의 첫 번째 stage 사용
+ if (!targetStage.length) {
+ targetStage = await db
+ .select({ id: issueStages.id, stageName: issueStages.stageName })
+ .from(issueStages)
+ .where(eq(issueStages.documentId, docId))
+ .orderBy(issueStages.id) // 첫 번째 stage
+ .limit(1)
+ }
+
+ if (!targetStage.length) {
+ return NextResponse.json({
+ error: "No stages found for this document"
+ }, { status: 400 })
+ }
+
+ const stage = targetStage[0].stageName
+ const issueStageId = targetStage[0].id
+
+ /* ------- 트랜잭션 ------- */
+ const result = await db.transaction(async (tx) => {
+ /* Revision 생성 */
+ const today = new Date().toISOString().slice(0, 10)
+
+ // 동일한 revision이 이미 있는지 확인 (usage, usageType도 포함)
+ const whereConditions = [
+ eq(revisions.issueStageId, issueStageId),
+ eq(revisions.revision, revision),
+ eq(revisions.usage, usage)
+ ]
+
+ // usageType이 있는 경우에만 조건에 추가
+ if (usageType) {
+ whereConditions.push(eq(revisions.usageType, usageType))
+ }
+
+ const [existingRev] = await tx
+ .select()
+ .from(revisions)
+ .where(and(...whereConditions))
+ .limit(1)
+
+ let revisionId: number
+ let revisionData: any
+
+ if (existingRev) {
+ // 기존 revision 업데이트
+ const updateData: any = {
+ uploaderName: uploaderName ?? existingRev.uploaderName,
+ comment: comment ?? existingRev.comment,
+ updatedAt: new Date(),
+ }
+
+ // usage는 항상 업데이트
+ updateData.usage = usage
+
+ // usageType이 있는 경우에만 업데이트
+ if (usageType) {
+ updateData.usageType = usageType
+ }
+
+ await tx.update(revisions)
+ .set(updateData)
+ .where(eq(revisions.id, existingRev.id))
+
+ const [updated] = await tx
+ .select()
+ .from(revisions)
+ .where(eq(revisions.id, existingRev.id))
+
+ revisionId = existingRev.id
+ revisionData = updated
+
+ await logRevisionChange(
+ docInfo.contractId,
+ revisionId,
+ "UPDATE",
+ updated,
+ existingRev,
+ undefined,
+ uploaderName ?? undefined,
+ [targetSystem]
+ )
+ } else {
+ // 새 revision 생성
+ const [newRev] = await tx.insert(revisions)
+ .values({
+ issueStageId,
+ revision,
+ usage,
+ usageType,
+ uploaderType: "vendor",
+ uploaderName: uploaderName ?? undefined,
+ revisionStatus: "UPLOADED",
+ uploadedAt: today,
+ comment: comment ?? undefined,
+ updatedAt: new Date(),
+ })
+ .returning()
+
+ revisionId = newRev.id
+ revisionData = newRev
+
+ await logRevisionChange(
+ docInfo.contractId,
+ revisionId,
+ "CREATE",
+ newRev,
+ undefined,
+ undefined,
+ uploaderName ?? undefined,
+ [targetSystem]
+ )
+ }
+
+ /* 첨부파일 처리 */
+ const uploadedFiles: any[] = []
+ const baseDir = join(process.cwd(), "public", "documents")
+
+ for (const file of attachmentFiles) {
+ const ext = path.extname(file.name)
+ const fname = uuidv4() + ext
+ const dest = join(baseDir, fname)
+
+ await writeFile(dest, Buffer.from(await file.arrayBuffer()))
+
+ const [att] = await tx.insert(documentAttachments)
+ .values({
+ revisionId,
+ fileName: file.name,
+ filePath: "/documents/" + fname,
+ fileSize: file.size,
+ fileType: ext.slice(1).toLowerCase() || undefined,
+ updatedAt: new Date(),
+ })
+ .returning()
+
+ uploadedFiles.push({
+ id: att.id,
+ fileName: file.name,
+ fileSize: file.size,
+ filePath: att.filePath,
+ fileType: ext.slice(1).toLowerCase() || null, // ✅ 추가
+ })
+
+ // change_logs: attachment CREATE
+ await logAttachmentChange(
+ docInfo.contractId,
+ att.id,
+ "CREATE",
+ att,
+ undefined,
+ undefined,
+ uploaderName ?? undefined,
+ [targetSystem]
+ )
+ }
+
+ /* documents.updatedAt 업데이트 */
+ await tx.update(documents)
+ .set({ updatedAt: new Date() })
+ .where(eq(documents.id, docId))
+
+ return {
+ revisionId,
+ issueStageId, // ✅ 추가
+ stage,
+ revision,
+ uploadedFiles,
+ contractId: docInfo.contractId,
+ usage,
+ usageType
+ }
+ })
+
+ // 캐시 무효화
+ try {
+ revalidateTag(`sync-status-${result.contractId}`)
+
+ console.log(`✅ Cache invalidated for contract ${result.contractId}`)
+ } catch (cacheError) {
+ console.warn('⚠️ Cache invalidation failed:', cacheError)
+ }
+
+ return NextResponse.json({
+ success: true,
+ message: `리비전 ${result.revision}이 성공적으로 업로드되었습니다`,
+ data: {
+ revisionId: result.revisionId,
+ issueStageId: issueStageId, // ✅ 추가
+ stage: result.stage,
+ revision: result.revision,
+ usage: result.usage,
+ usageType: result.usageType,
+ uploaderName: uploaderName, // ✅ 추가
+ uploadedFiles: result.uploadedFiles,
+ filesCount: result.uploadedFiles.length
+ },
+ })
+ } catch (e) {
+ console.error("revision-upload error:", e)
+ return NextResponse.json(
+ { error: "Failed to upload revision", details: String(e) },
+ { status: 500 },
+ )
+ }
+} \ No newline at end of file