diff options
Diffstat (limited to 'app/api/vendors/route.ts')
| -rw-r--r-- | app/api/vendors/route.ts | 248 |
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 |
