summaryrefslogtreecommitdiff
path: root/app/api/vendors/route.ts
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-07-07 01:43:36 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-07-07 01:43:36 +0000
commitfbb3b7f05737f9571b04b0a8f4f15c0928de8545 (patch)
tree343247117a7587b8ef5c418c9528d1cf2e0b6f1c /app/api/vendors/route.ts
parent9945ad119686a4c3a66f7b57782750f78a366cfb (diff)
(대표님) 변경사항 20250707 10시 43분
Diffstat (limited to 'app/api/vendors/route.ts')
-rw-r--r--app/api/vendors/route.ts248
1 files changed, 248 insertions, 0 deletions
diff --git a/app/api/vendors/route.ts b/app/api/vendors/route.ts
new file mode 100644
index 00000000..7c7dbb84
--- /dev/null
+++ b/app/api/vendors/route.ts
@@ -0,0 +1,248 @@
+// app/api/vendors/route.ts
+import { NextRequest, NextResponse } from 'next/server'
+import { unstable_noStore } from 'next/cache'
+import { revalidateTag } from 'next/cache'
+import { randomUUID } from 'crypto'
+import * as fs from 'fs/promises'
+import * as path from 'path'
+import { eq } from 'drizzle-orm'
+import { PgTransaction } from 'drizzle-orm/pg-core'
+
+import db from '@/db/db'
+import { users, vendors, vendorContacts, vendorAttachments } from '@/db/schema'
+import { insertVendor } from '@/lib/vendors/repository'
+import { getErrorMessage } from '@/lib/handle-error'
+
+// Types
+interface CreateVendorData {
+ vendorName: string
+ vendorCode?: string
+ address?: string
+ country?: string
+ phone?: string
+ email: string
+ website?: string
+ status?: string
+ taxId: string
+ vendorTypeId: number
+ items?: string
+ representativeName?: string
+ representativeBirth?: string
+ representativeEmail?: string
+ representativePhone?: string
+ corporateRegistrationNumber?: string
+}
+
+interface ContactData {
+ contactName: string
+ contactPosition?: string
+ contactEmail: string
+ contactPhone?: string
+ isPrimary?: boolean
+}
+
+// File attachment types
+const FILE_TYPES = {
+ BUSINESS_REGISTRATION: 'BUSINESS_REGISTRATION',
+ ISO_CERTIFICATION: 'ISO_CERTIFICATION',
+ CREDIT_REPORT: 'CREDIT_REPORT',
+ BANK_ACCOUNT_COPY: 'BANK_ACCOUNT_COPY'
+} as const
+
+type FileType = typeof FILE_TYPES[keyof typeof FILE_TYPES]
+
+async function storeVendorFiles(
+ tx: PgTransaction<any, any, any>,
+ vendorId: number,
+ files: File[],
+ attachmentType: FileType
+) {
+ const vendorDir = path.join(
+ process.cwd(),
+ "public",
+ "vendors",
+ String(vendorId)
+ )
+ await fs.mkdir(vendorDir, { recursive: true })
+
+ for (const file of files) {
+ // Convert file to buffer
+ const ab = await file.arrayBuffer()
+ const buffer = Buffer.from(ab)
+
+ // Generate a unique filename
+ const uniqueName = `${randomUUID()}-${file.name}`
+ const relativePath = path.join("vendors", String(vendorId), uniqueName)
+ const absolutePath = path.join(process.cwd(), "public", relativePath)
+
+ // Write to disk
+ await fs.writeFile(absolutePath, buffer)
+
+ // Insert attachment record
+ await tx.insert(vendorAttachments).values({
+ vendorId,
+ fileName: file.name,
+ filePath: "/" + relativePath.replace(/\\/g, "/"),
+ attachmentType,
+ })
+ }
+}
+
+export async function POST(request: NextRequest) {
+ unstable_noStore()
+
+ try {
+ const formData = await request.formData()
+
+ // Parse vendor data and contacts from JSON strings
+ const vendorDataString = formData.get('vendorData') as string
+ const contactsString = formData.get('contacts') as string
+
+ if (!vendorDataString || !contactsString) {
+ return NextResponse.json(
+ { error: 'Missing vendor data or contacts' },
+ { status: 400 }
+ )
+ }
+
+ const vendorData: CreateVendorData = JSON.parse(vendorDataString)
+ const contacts: ContactData[] = JSON.parse(contactsString)
+
+ // Extract files by type
+ const businessRegistrationFiles = formData.getAll('businessRegistration') as File[]
+ const isoCertificationFiles = formData.getAll('isoCertification') as File[]
+ const creditReportFiles = formData.getAll('creditReport') as File[]
+ const bankAccountFiles = formData.getAll('bankAccount') as File[]
+
+ // Validate required files
+ if (businessRegistrationFiles.length === 0) {
+ return NextResponse.json(
+ { error: '사업자등록증을 업로드해주세요.' },
+ { status: 400 }
+ )
+ }
+
+ if (isoCertificationFiles.length === 0) {
+ return NextResponse.json(
+ { error: 'ISO 인증서를 업로드해주세요.' },
+ { status: 400 }
+ )
+ }
+
+ if (creditReportFiles.length === 0) {
+ return NextResponse.json(
+ { error: '신용평가보고서를 업로드해주세요.' },
+ { status: 400 }
+ )
+ }
+
+ if (vendorData.country !== "KR" && bankAccountFiles.length === 0) {
+ return NextResponse.json(
+ { error: '대금지급 통장사본을 업로드해주세요.' },
+ { status: 400 }
+ )
+ }
+
+ // Check for existing email
+ const existingUser = await db
+ .select({ id: users.id })
+ .from(users)
+ .where(eq(users.email, vendorData.email))
+ .limit(1)
+
+ if (existingUser.length > 0) {
+ return NextResponse.json(
+ {
+ error: `이미 등록된 이메일입니다. 다른 이메일을 사용해주세요. (Email ${vendorData.email} already exists in the system)`
+ },
+ { status: 400 }
+ )
+ }
+
+ // Check for existing taxId
+ const existingVendor = await db
+ .select({ id: vendors.id })
+ .from(vendors)
+ .where(eq(vendors.taxId, vendorData.taxId))
+ .limit(1)
+
+ if (existingVendor.length > 0) {
+ return NextResponse.json(
+ {
+ error: `이미 등록된 사업자등록번호입니다. (Tax ID ${vendorData.taxId} already exists in the system)`
+ },
+ { status: 400 }
+ )
+ }
+
+ // Create vendor and handle files in transaction
+ await db.transaction(async (tx) => {
+ // Insert the vendor
+ const [newVendor] = await insertVendor(tx, {
+ vendorName: vendorData.vendorName,
+ vendorCode: vendorData.vendorCode || null,
+ address: vendorData.address || null,
+ country: vendorData.country || null,
+ phone: vendorData.phone || null,
+ email: vendorData.email,
+ website: vendorData.website || null,
+ status: vendorData.status ?? "PENDING_REVIEW",
+ taxId: vendorData.taxId,
+ vendorTypeId: vendorData.vendorTypeId,
+ items: vendorData.items || null,
+
+ // Representative info
+ representativeName: vendorData.representativeName || null,
+ representativeBirth: vendorData.representativeBirth || null,
+ representativeEmail: vendorData.representativeEmail || null,
+ representativePhone: vendorData.representativePhone || null,
+ corporateRegistrationNumber: vendorData.corporateRegistrationNumber || null,
+ representativeWorkExpirence: vendorData.representativeWorkExpirence || false,
+
+ })
+
+ // Store files by type
+ if (businessRegistrationFiles.length > 0) {
+ await storeVendorFiles(tx, newVendor.id, businessRegistrationFiles, FILE_TYPES.BUSINESS_REGISTRATION)
+ }
+
+ if (isoCertificationFiles.length > 0) {
+ await storeVendorFiles(tx, newVendor.id, isoCertificationFiles, FILE_TYPES.ISO_CERTIFICATION)
+ }
+
+ if (creditReportFiles.length > 0) {
+ await storeVendorFiles(tx, newVendor.id, creditReportFiles, FILE_TYPES.CREDIT_REPORT)
+ }
+
+ if (bankAccountFiles.length > 0) {
+ await storeVendorFiles(tx, newVendor.id, bankAccountFiles, FILE_TYPES.BANK_ACCOUNT_COPY)
+ }
+
+ // Insert contacts
+ for (const contact of contacts) {
+ await tx.insert(vendorContacts).values({
+ vendorId: newVendor.id,
+ contactName: contact.contactName,
+ contactPosition: contact.contactPosition || null,
+ contactEmail: contact.contactEmail,
+ contactPhone: contact.contactPhone || null,
+ isPrimary: contact.isPrimary ?? false,
+ })
+ }
+ })
+
+ revalidateTag("vendors")
+
+ return NextResponse.json(
+ { message: '벤더 등록이 완료되었습니다.' },
+ { status: 201 }
+ )
+
+ } catch (error) {
+ console.error('Vendor creation error:', error)
+ return NextResponse.json(
+ { error: getErrorMessage(error) },
+ { status: 500 }
+ )
+ }
+} \ No newline at end of file