diff options
Diffstat (limited to 'lib/tech-vendors/service.ts')
| -rw-r--r-- | lib/tech-vendors/service.ts | 441 |
1 files changed, 361 insertions, 80 deletions
diff --git a/lib/tech-vendors/service.ts b/lib/tech-vendors/service.ts index 657314e6..7513a283 100644 --- a/lib/tech-vendors/service.ts +++ b/lib/tech-vendors/service.ts @@ -34,7 +34,7 @@ import type { CreateTechVendorItemSchema, } from "./validations"; -import { asc, desc, ilike, inArray, and, or, eq, isNull } from "drizzle-orm"; +import { asc, desc, ilike, inArray, and, or, eq, isNull, not } from "drizzle-orm"; import path from "path"; import fs from "fs/promises"; import { randomUUID } from "crypto"; @@ -144,9 +144,6 @@ export async function getTechVendorStatusCounts() { "ACTIVE": 0, "INACTIVE": 0, "BLACKLISTED": 0, - "PENDING_REVIEW": 0, - "IN_REVIEW": 0, - "REJECTED": 0 }; const result = await db.transaction(async (tx) => { @@ -267,6 +264,11 @@ export async function createTechVendor(input: CreateTechVendorSchema) { taxId: input.taxId, address: input.address || null, country: input.country, + countryEng: null, + countryFab: null, + agentName: null, + agentPhone: null, + agentEmail: null, phone: input.phone || null, email: input.email, website: input.website || null, @@ -275,9 +277,8 @@ export async function createTechVendor(input: CreateTechVendorSchema) { representativeBirth: input.representativeBirth || null, representativeEmail: input.representativeEmail || null, representativePhone: input.representativePhone || null, - corporateRegistrationNumber: input.corporateRegistrationNumber || null, items: input.items || null, - status: "PENDING_REVIEW" + status: "ACTIVE" }); // 2. 연락처 정보 등록 @@ -547,8 +548,10 @@ export async function getTechVendorItems(input: GetTechVendorItemsSchema, id: nu export interface ItemDropdownOption { itemCode: string; - itemName: string; - description: string | null; + itemList: string; + workType: string | null; + shipTypes: string | null; + subItemList: string | null; } /** @@ -559,31 +562,140 @@ export async function getItemsForTechVendor(vendorId: number) { return unstable_cache( async () => { try { - // 해당 vendorId가 이미 가지고 있는 itemCode 목록을 서브쿼리로 구함 - // 그 아이템코드를 제외(notIn)하여 모든 items 테이블에서 조회 - const itemsData = await db + // 1. 벤더 정보 조회로 벤더 타입 확인 + const vendor = await db.query.techVendors.findFirst({ + where: eq(techVendors.id, vendorId), + columns: { + techVendorType: true + } + }); + + if (!vendor) { + return { + data: [], + error: "벤더를 찾을 수 없습니다.", + }; + } + + // 2. 해당 벤더가 이미 가지고 있는 itemCode 목록 조회 + const existingItems = await db .select({ - itemCode: items.itemCode, - itemName: items.itemName, - description: items.description, + itemCode: techVendorPossibleItems.itemCode, }) - .from(items) - .leftJoin( - techVendorPossibleItems, - eq(items.itemCode, techVendorPossibleItems.itemCode) - ) - // vendorPossibleItems.vendorId가 이 vendorId인 행이 없는(즉 아직 등록되지 않은) 아이템만 - .where( - isNull(techVendorPossibleItems.id) - ) - .orderBy(asc(items.itemName)); + .from(techVendorPossibleItems) + .where(eq(techVendorPossibleItems.vendorId, vendorId)); + + const existingItemCodes = existingItems.map(item => item.itemCode); + + // 3. 벤더 타입에 따라 해당 타입의 아이템만 조회 + // let availableItems: ItemDropdownOption[] = []; + let availableItems: (typeof itemShipbuilding.$inferSelect | typeof itemOffshoreTop.$inferSelect | typeof itemOffshoreHull.$inferSelect)[] = []; + switch (vendor.techVendorType) { + case "조선": + const shipbuildingItems = await db + .select({ + id: itemShipbuilding.id, + createdAt: itemShipbuilding.createdAt, + updatedAt: itemShipbuilding.updatedAt, + itemCode: itemShipbuilding.itemCode, + itemList: itemShipbuilding.itemList, + workType: itemShipbuilding.workType, + shipTypes: itemShipbuilding.shipTypes, + }) + .from(itemShipbuilding) + .where( + existingItemCodes.length > 0 + ? not(inArray(itemShipbuilding.itemCode, existingItemCodes)) + : undefined + ) + .orderBy(asc(itemShipbuilding.itemCode)); + + availableItems = shipbuildingItems + .filter(item => item.itemCode != null) + .map(item => ({ + id: item.id, + createdAt: item.createdAt, + updatedAt: item.updatedAt, + itemCode: item.itemCode!, + itemList: item.itemList || "조선 아이템", + workType: item.workType || "조선 관련 아이템", + shipTypes: item.shipTypes || "조선 관련 아이템" + })); + break; + + case "해양TOP": + const offshoreTopItems = await db + .select({ + id: itemOffshoreTop.id, + createdAt: itemOffshoreTop.createdAt, + updatedAt: itemOffshoreTop.updatedAt, + itemCode: itemOffshoreTop.itemCode, + itemList: itemOffshoreTop.itemList, + workType: itemOffshoreTop.workType, + subItemList: itemOffshoreTop.subItemList, + }) + .from(itemOffshoreTop) + .where( + existingItemCodes.length > 0 + ? not(inArray(itemOffshoreTop.itemCode, existingItemCodes)) + : undefined + ) + .orderBy(asc(itemOffshoreTop.itemCode)); + + availableItems = offshoreTopItems + .filter(item => item.itemCode != null) + .map(item => ({ + id: item.id, + createdAt: item.createdAt, + updatedAt: item.updatedAt, + itemCode: item.itemCode!, + itemList: item.itemList || "해양TOP 아이템", + workType: item.workType || "해양TOP 관련 아이템", + subItemList: item.subItemList || "해양TOP 관련 아이템" + })); + break; + + case "해양HULL": + const offshoreHullItems = await db + .select({ + id: itemOffshoreHull.id, + createdAt: itemOffshoreHull.createdAt, + updatedAt: itemOffshoreHull.updatedAt, + itemCode: itemOffshoreHull.itemCode, + itemList: itemOffshoreHull.itemList, + workType: itemOffshoreHull.workType, + subItemList: itemOffshoreHull.subItemList, + }) + .from(itemOffshoreHull) + .where( + existingItemCodes.length > 0 + ? not(inArray(itemOffshoreHull.itemCode, existingItemCodes)) + : undefined + ) + .orderBy(asc(itemOffshoreHull.itemCode)); + + availableItems = offshoreHullItems + .filter(item => item.itemCode != null) + .map(item => ({ + id: item.id, + createdAt: item.createdAt, + updatedAt: item.updatedAt, + itemCode: item.itemCode!, + itemList: item.itemList || "해양HULL 아이템", + workType: item.workType || "해양HULL 관련 아이템", + subItemList: item.subItemList || "해양HULL 관련 아이템" + })); + break; + + default: + return { + data: [], + error: `지원하지 않는 벤더 타입입니다: ${vendor.techVendorType}`, + }; + } return { - data: itemsData.map((item) => ({ - itemCode: item.itemCode ?? "", // null이라면 ""로 치환 - itemName: item.itemName, - description: item.description ?? "" // null이라면 ""로 치환 - })), + data: availableItems, error: null }; } catch (err) { @@ -827,7 +939,7 @@ export async function rejectTechVendors(input: ApproveTechVendorsInput) { const [updated] = await tx .update(techVendors) .set({ - status: "REJECTED", + status: "INACTIVE", updatedAt: new Date() }) .where(inArray(techVendors.id, input.ids.map(id => parseInt(id)))) @@ -918,7 +1030,6 @@ export async function exportTechVendorDetails(vendorIds: number[]) { representativeEmail: techVendors.representativeEmail, representativePhone: techVendors.representativePhone, representativeBirth: techVendors.representativeBirth, - corporateRegistrationNumber: techVendors.corporateRegistrationNumber, items: techVendors.items, createdAt: techVendors.createdAt, updatedAt: techVendors.updatedAt, @@ -1090,75 +1201,124 @@ export const findVendorById = async (id: number): Promise<TechVendor | null> => export async function importTechVendorsFromExcel( vendors: Array<{ vendorName: string; + vendorCode?: string | null; email: string; taxId: string; - address?: string; - country?: string; - phone?: string; - website?: string; + country?: string | null; + countryEng?: string | null; + countryFab?: string | null; + agentName?: string | null; + agentPhone?: string | null; + agentEmail?: string | null; + address?: string | null; + phone?: string | null; + website?: string | null; techVendorType: string; - items: string; // 쉼표로 구분된 아이템 코드들 + representativeName?: string | null; + representativeEmail?: string | null; + representativePhone?: string | null; + representativeBirth?: string | null; + items: string; }>, ) { unstable_noStore(); try { + console.log("Import 시작 - 벤더 수:", vendors.length); + console.log("첫 번째 벤더 데이터:", vendors[0]); + const result = await db.transaction(async (tx) => { const createdVendors = []; for (const vendor of vendors) { - // 1. 벤더 생성 - const [newVendor] = await tx.insert(techVendors).values({ - vendorName: vendor.vendorName, - vendorCode: null, // 자동 생성 - taxId: vendor.taxId, - address: vendor.address || null, - country: vendor.country || null, - phone: vendor.phone || null, - email: vendor.email, - website: vendor.website || null, - techVendorType: vendor.techVendorType as "조선" | "해양TOP" | "해양HULL", - status: "PENDING_REVIEW" - }).returning(); - - // 2. 유저 생성 (이메일이 있는 경우) - if (vendor.email) { - // 이미 존재하는 유저인지 확인 - const existingUser = await tx.query.users.findFirst({ - where: eq(users.email, vendor.email), - columns: { id: true } + console.log("벤더 처리 시작:", vendor.vendorName); + + try { + // 1. 벤더 생성 + console.log("벤더 생성 시도:", { + vendorName: vendor.vendorName, + email: vendor.email, + techVendorType: vendor.techVendorType }); - // 유저가 존재하지 않는 경우에만 생성 - if (!existingUser) { - await tx.insert(users).values({ - name: vendor.vendorName, - email: vendor.email, - companyId: newVendor.id, - domain: "partners", + const [newVendor] = await tx.insert(techVendors).values({ + vendorName: vendor.vendorName, + vendorCode: vendor.vendorCode || null, + taxId: vendor.taxId, + country: vendor.country || null, + countryEng: vendor.countryEng || null, + countryFab: vendor.countryFab || null, + agentName: vendor.agentName || null, + agentPhone: vendor.agentPhone || null, + agentEmail: vendor.agentEmail || null, + address: vendor.address || null, + phone: vendor.phone || null, + email: vendor.email, + website: vendor.website || null, + techVendorType: vendor.techVendorType as "조선" | "해양TOP" | "해양HULL", + status: "ACTIVE", + representativeName: vendor.representativeName || null, + representativeEmail: vendor.representativeEmail || null, + representativePhone: vendor.representativePhone || null, + representativeBirth: vendor.representativeBirth || null, + }).returning(); + + console.log("벤더 생성 성공:", newVendor.id); + + // 2. 유저 생성 (이메일이 있는 경우) + if (vendor.email) { + console.log("유저 생성 시도:", vendor.email); + + // 이미 존재하는 유저인지 확인 + const existingUser = await tx.query.users.findFirst({ + where: eq(users.email, vendor.email), + columns: { id: true } }); - } - } - // 3. 아이템 등록 - if (vendor.items) { - const itemCodes = vendor.items.split(',').map(code => code.trim()); - for (const itemCode of itemCodes) { - // 아이템 정보 조회 - const [item] = await tx.select().from(items).where(eq(items.itemCode, itemCode)); - if (item && item.itemCode && item.itemName) { - await tx.insert(techVendorPossibleItems).values({ - vendorId: newVendor.id, - itemCode: item.itemCode, - itemName: item.itemName, + // 유저가 존재하지 않는 경우에만 생성 + if (!existingUser) { + await tx.insert(users).values({ + name: vendor.vendorName, + email: vendor.email, + techCompanyId: newVendor.id, // techCompanyId 설정 + domain: "partners", }); + console.log("유저 생성 성공"); + } else { + console.log("이미 존재하는 유저:", existingUser.id); } } - } - createdVendors.push(newVendor); + // // 3. 아이템 등록 + // if (vendor.items) { + // console.log("아이템 등록 시도:", vendor.items); + // const itemCodes = vendor.items.split(',').map(code => code.trim()); + + // for (const itemCode of itemCodes) { + // // 아이템 정보 조회 + // const [item] = await tx.select().from(items).where(eq(items.itemCode, itemCode)); + // if (item && item.itemCode && item.itemName) { + // await tx.insert(techVendorPossibleItems).values({ + // vendorId: newVendor.id, + // itemCode: item.itemCode, + // itemName: item.itemName, + // }); + // console.log("아이템 등록 성공:", itemCode); + // } else { + // console.log("아이템을 찾을 수 없음:", itemCode); + // } + // } + // } + + createdVendors.push(newVendor); + console.log("벤더 처리 완료:", vendor.vendorName); + } catch (error) { + console.error("벤더 처리 중 오류 발생:", vendor.vendorName, error); + throw error; + } } + console.log("모든 벤더 처리 완료. 생성된 벤더 수:", createdVendors.length); return createdVendors; }); @@ -1166,9 +1326,130 @@ export async function importTechVendorsFromExcel( revalidateTag("tech-vendors"); revalidateTag("users"); + console.log("Import 완료 - 결과:", result); + return { success: true, data: result }; + } catch (error) { + console.error("Import 실패:", error); + return { success: false, error: getErrorMessage(error) }; + } +} + +export async function findTechVendorById(id: number): Promise<TechVendor | null> { + const result = await db + .select() + .from(techVendors) + .where(eq(techVendors.id, id)) + .limit(1) + + return result[0] || null +} + +/** + * 단일 기술영업 벤더 추가 (사용자 계정도 함께 생성) + */ +export async function addTechVendor(input: { + vendorName: string; + vendorCode?: string | null; + email: string; + taxId: string; + country?: string | null; + countryEng?: string | null; + countryFab?: string | null; + agentName?: string | null; + agentPhone?: string | null; + agentEmail?: string | null; + address?: string | null; + phone?: string | null; + website?: string | null; + techVendorType: "조선" | "해양TOP" | "해양HULL"; + representativeName?: string | null; + representativeEmail?: string | null; + representativePhone?: string | null; + representativeBirth?: string | null; +}) { + unstable_noStore(); + + try { + console.log("벤더 추가 시작:", input.vendorName); + + const result = await db.transaction(async (tx) => { + // 1. 이메일 중복 체크 + const existingVendor = await tx.query.techVendors.findFirst({ + where: eq(techVendors.email, input.email), + columns: { id: true, vendorName: true } + }); + + if (existingVendor) { + throw new Error(`이미 등록된 이메일입니다: ${input.email} (업체명: ${existingVendor.vendorName})`); + } + + // 2. 벤더 생성 + console.log("벤더 생성 시도:", { + vendorName: input.vendorName, + email: input.email, + techVendorType: input.techVendorType + }); + + const [newVendor] = await tx.insert(techVendors).values({ + vendorName: input.vendorName, + vendorCode: input.vendorCode || null, + taxId: input.taxId, + country: input.country || null, + countryEng: input.countryEng || null, + countryFab: input.countryFab || null, + agentName: input.agentName || null, + agentPhone: input.agentPhone || null, + agentEmail: input.agentEmail || null, + address: input.address || null, + phone: input.phone || null, + email: input.email, + website: input.website || null, + techVendorType: input.techVendorType, + status: "ACTIVE", + representativeName: input.representativeName || null, + representativeEmail: input.representativeEmail || null, + representativePhone: input.representativePhone || null, + representativeBirth: input.representativeBirth || null, + }).returning(); + + console.log("벤더 생성 성공:", newVendor.id); + + // 3. 유저 생성 (techCompanyId 설정) + console.log("유저 생성 시도:", input.email); + + // 이미 존재하는 유저인지 확인 + const existingUser = await tx.query.users.findFirst({ + where: eq(users.email, input.email), + columns: { id: true } + }); + + let userId = null; + // 유저가 존재하지 않는 경우에만 생성 + if (!existingUser) { + const [newUser] = await tx.insert(users).values({ + name: input.vendorName, + email: input.email, + techCompanyId: newVendor.id, // techCompanyId 설정 + domain: "partners", + }).returning(); + userId = newUser.id; + console.log("유저 생성 성공:", userId); + } else { + console.log("이미 존재하는 유저:", existingUser.id); + } + + return { vendor: newVendor, userId }; + }); + + // 캐시 무효화 + revalidateTag("tech-vendors"); + revalidateTag("users"); + + console.log("벤더 추가 완료:", result); return { success: true, data: result }; } catch (error) { - console.error("Failed to import tech vendors:", error); + console.error("벤더 추가 실패:", error); return { success: false, error: getErrorMessage(error) }; } -}
\ No newline at end of file +} + |
